New and delete from a list would be good examples. In those cases, a blank record is added or the current record is deleted by the ViewModel. Any action taken by the view should be in response to those events occurring.
One way would be to use a command parameter object that the business layer can modify and your VM can process after the command in executed.
For your delete message box case, I abstract out messageboxes via an interface. Similar to this. I also inject these interfaces for my WPF app.
Constructor
public MyViewModel(IMessage msg)
{
_msg = msg;
}
Then, in the method delete method on the ViewModel...something like
public void Delete()
{
if(CanDelete)
{
//do the delete
}
else
{
_msg.Show("You can't delete this record");
}
}
This will make it testable, you can plug in a different IMessage implementations that don't actually show a messagebox. Those might just print to the console, for testing purposes. Obviously your WPF app might have an implementation like
public class MessageBoxQuestion : IMessage
{
public void Show(string message)
{
MessageBox.Show(message);
}
}
Doing this makes testing the different routes(think Yes/No dialogs) very easy and straight forward. You can imagine a delete confirmation. You could either use a concrete instance of the IMessage to return true/false for confirmation or mock out the container during your test.
[Test]
public void Can_Cancel_Delete()
{
var vm = new ProductViewModel(_cancel);
...
}
[Test]
public void Can_Confirm_Delete()
{
var vm = new ProductViewModel(_yes);
...
}
For your other question on when to use the Command, I instantiate the Add New or Details views from the View in question. Just as you have in your example. Views are only instantiated by other Views in our app. I don't use a command in those cases. I do however use the ViewModel properties of the parent view to the child view.
public void Object_DoubleClick(object sender, EventArgs e)
{
var detailView = new DetailView(ViewModel.Product);
detailView.Show();
}
Hope this helps!
Never thought I'd see myself being quoted in a question.
I pondered this question myself for some time, and made a rather pragmatic decision for my code base:
In my code base, the ViewModel is getting called when actions happen, and I wanted it to stay this way. Additionally I do not want the ViewModel to control the views.
What did I do?
I added a controller for navigation:
public interface INavigation
{
void NewContent(ViewModel viewmodel);
void NewWindow(ViewModel viewmodel);
}
This controller does contain two actions: NewContent() does show new content in the current window, NewWindow() creates a new Window, populates it with the content and shows it.
Of course my viewmodels have no clue which view to show. But they do know which viewmodel they want to show, so according to your example when DeleteCommand is executed, it would call the navigation service function NewWindow(new ValidateCustomerDeletedViewModel()) to show a window stating 'the customer has been deleted' (overkill for this simple messagebox, but it would be easy to have a special navigator function for simple messageboxes).
How does the viewmodel get the navigation service?
My viewmodel class has a property for the navigation controller:
public class ViewModel
{
public INavigation Navigator { get; set; }
[...]
}
When a viewmodel is attached to a window (or whatever displays the view), the window will set the Navigator property, so the viewmodel can call it.
How does the navigator create the view to the viewmodel?
You could have a simple list which view to create for which viewmodel, in my case I can use simple reflection since the names are matching:
public static FrameworkElement CreateView(ViewModel viewmodel)
{
Type vmt = viewmodel.GetType();
// big bad dirty hack to get the name of the view, but it works *cough*
Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, "));
return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}
Of course the view needs a constructor accepting the viewmodel as parameter:
public partial class ValidateCustomerDeletedView : UserControl
{
public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
{
InitializeComponent();
this.DataContext = dac;
}
}
How does my window look like?
Simple: my main window does implement the INavigation interface, and shows a start page on creation. See for yourself:
public partial class MainWindow : Window, INavigation
{
public MainWindow()
{
InitializeComponent();
NewContent(new StartPageViewModel());
}
public MainWindow(ViewModel newcontrol)
{
InitializeComponent();
NewContent(newcontrol);
}
#region INavigation Member
public void NewContent(ViewModel newviewmodel)
{
newviewmodel.Navigator = this;
FrameworkElement ui = App.CreateView(newviewmodel);
this.Content = ui;
this.DataContext = ui.DataContext;
}
public void NewWindow(ViewModel viewModel)
{
MainWindow newwindow = new MainWindow(viewModel);
newwindow.Show();
}
#endregion
}
(This does work equally well with a NavigationWindow and wrapping the view into a Page)
Of course this is testable, since the navigation controller can be mocked easily.
I'm not really sure if this is a perfect solution, but it works nicely for me right now. Any ideas and comments are welcome!