Maintaining the layering of MVVM (Model-View-ViewModel) when displaying a dialog form is challenging, because the command handler in the
ViewModel layer needs to somehow access the dialog window without breaking the layering. The
ViewModel should not have a compile-time dependency on any View (Windows UI) object, including the View that contains the dialog window, while the dialog window needs to be given a reference to the data it will manipulate, which is not known until runtime. This article describes a simplified approach using a modified Visitor Pattern (Gamma, et al, Design Patterns) and dependency injection (DI) to maintain the separation of layers.
The Visitor Pattern separates data from the operations to be performed on it. In this case, a dialog form needs to update or populate the fields of an object. The
Visitor achieves this by having a class with overloaded methods, each accepting a specific object type (class) argument. Thus, when a "
Visit" method is invoked with a specific argument type, the correct method is automatically chosen. These
Visit methods are responsible for creating the correct dialog window, assigning the argument object as the
DataContext of the dialog, and showing the dialog (we're assuming a modal dialog here). The
Visitor is injected into the
ViewModel (VM) objects in the
MainWindow-loaded event handler by property injection (the
ViewModel object(s) have a
public Visitor property field to hold the reference to the
Visitor). I believe this is simpler than using a mediator, since there is no need for events to pass between the layers.
This example does not require a Dependency Injection container, although one could be applied with little difficulty. It does not reference Prism Behaviors or other external frameworks. It can be added to an existing code base with no disruption.
Using the Code
ViewModel uses the Relay Command (Hall, Pro WPF and Silverlight MVVM, Apress) to provide the command behavior. The VM objects are created in XAML as static resources. The
Loaded event handler retrieves these objects, instantiates the
Visitor class, and injects it into the VM objects by setting their
Visitor property. The main window buttons are bound to the commands in XAML. When invoked, the command handler either creates a new data object, or retrieves the one currently selected, and invokes the
DynamicVisitor method, which invokes the overloaded
Visit method matching the argument.
The main point of this article is the
ViewModel layer defines an interface,
IDialogVisitor, with one method,
ViewModel classes contain a public reference to the interface class. Thus, the
ViewModel classes (
ViewVehicles) have a compile-time dependency only on the
Model layers. It is important to note that the interface does not define any of the methods for showing the dialog windows.
public interface DialogVisitor
object DynamicVisit(Object data);
View layer defines a derived
DialogVisitor that overrides the
DynamicVisit method, supplying it with the method which calls the correct
Visit method, based on the
Visit method's signature, and defines the
private Visit methods. The
Visit methods handle instantiating and showing the dialog window that handles the object in their arguments. The
loaded event handler instantiates the
DynamicVisit class, and injects it (by property injection) into the
public class DialogVisitor : ViewModel.IDialogVisitor
public object DynamicVisit(Object data) => Visit((dynamic)data);
private Person Visit(Person p)
var dlg = new PersonDialog();
dlg.DataContext = p;
private Vehicle Visit(Vehicle v)
var dlg = new VehicleDialog();
dlg.DataContext = v;
Visitor is injected into the
ViewModel class, the
DynamicVisit method is invoked to show the dialog, for example:
public void NewPerson()
if (Visitor == null) return;
Person p = new Person();
Most of the code in the example is scaffolding to support and demonstrate the
Visitor class. The data argument could potentially contain information to control more complex behavior in the dialog handler. The
DialogVisitor can be easily extended with more
Visit methods, as long as each one has a distinct signature based on the type of the argument.
After compilation, run the program and click the "Add" buttons raise the dialogs to create some rows of data; highlight a row and observe that the "Update" button becomes enabled. Click an "Update" button to raise the dialog with the row data in the dialog. Modify it, and, when the dialog is closed, the row data will reflect the updates.
Unit testing is not demonstrated in this example. Because there is no dependency on any View or UI objects in the
ViewModel layer, unit testing can be done by instantiating test versions of the
DynamicVisitor class and injecting those into the
ViewModel classes under test.
DynamicView class can have only one method per dialog data type. Depending on the complexity of the application, there could be more than one
DynamicView class, potentially with
Visit methods having more than one argument.
This example uses the
MainWindow-loaded event handler to create the
DynamicView and inject it into the
ViewModel classes, so that the
ViewModel classes can have parameterless constructors. One could refactor the
ViewModel classes to accept the
DynamicView class as a constructor argument, and use the
ObjectDataProvider mechanisms in XAML to create the
ViewModel classes, injecting the
DynamicView. This is a matter of preference. In my view, the example mechanism makes it a bit clearer what is going on, and is similar to how a unit test would be set up.
- 2nd January, 2020: Initial version