Posted by Mark Withall: 2016-10-15
I recently saw a post on StackExchange about creating new windows in an MVVM-compatible way. This seems to me to be a common cause of confusion.
My interpretation of MVVM is that the ViewModel
should not know about the View
. As windows, and related UI elements, are clearly a part of the View
, it seems to me that creating them should be a responsibility of the View
.
That said, what we want is for a Command
in the ViewModel
to result in a new window being created. For example, an OpenFileDialog
. The requirement of the ViewModel
, however, is for a filename. The ViewModel
shouldn’t care that a new dialog window will be created to prompt the user for this filename.
When creating a ViewModel
it is often useful to assume you have two views: a GUI and a command-line interface. In this instance, the GUI request for a filename would spawn an open file dialog and the command-line version would have a text prompt.
Let’s look at a concrete example.
The ViewModel
needs an interface that is specific to its requirements. In this case, it wants a filename.
public interface FilenameProvider
{
string GetFilename();
}
Our View
could be a GUI, in which case the implementation of this interface could look something like:
public class GuiFilenameProvider : FilenameProvider
{
public string GetFilename()
{
var ofd = new OpenFileDialog {Title = "File", Filter = "All Files|*.*"};
return ofd.ShowDialog(Application.Current.MainWindow) == true ? ofd.FileName : null;
}
}
However, if we were working on a command-line version of our application, a different implementation of the interface would be needed. For example:
public class CommandLineFilenameProvider : FilenameProvider
{
public string GetFilename()
{
Console.WriteLine("Give me a file: ");
return Console.ReadLine();
}
}
The ViewModel
doesn’t care where it gets the filename from, as long as it gets one. It would just have access to the interface and use it when it needs a filename. Something like the following:
public class MyViewModel : Presenter
{
private readonly FilenameProvider _filenameProvider;
private readonly MyModel _model;
public MyViewModel(FilenameProvider filenameProvider, MyModel model)
{
_filenameProvider = filenameProvider;
_model = model;
}
public ICommand DoSomethingCommand => new Command(() =>
{
var filename = _filenameProvider.GetFilename();
if (filename != null)
_model.DoSomethingWithFilename(filename);
});
}
The ViewModel
doesn’t know that the filename is being provided by a GUI or by a command-line interface.
It should be possible to use this type of pattern in most cases, where the effect of a command in the ViewModel
is the creation of a new window. To me, the one obvious exception to this is where you just want to create a new instance of a window with no additional side effects. In this case, this feels like an entirely View
-based action and the creation of the window should be handled there.