One of the first issues you’ll run into when wading into the waters of MVVM is how to display model dialogs to the user while executing code in the view-model. Popping up a dialog, such as a MessageBox or a ChildWindow, from the view-model is an anti-pattern that violates the separation of concerns that exists between the view and the view-model. One of the main benefits of MVVM is better application maintainability. If you separate concerns of presentation (the view) from business logic (the view-model) and data (the model), making a change in one area is less likely to impact other areas. Another benefit is testability. It is notoriously difficult to test the user interface by simulating things like button clicks and mouse overs. Encapsulating functionality into a view-model means that you can test it independently of the view, which is just another consumer of the view-model. If you were to display a dialog from the view-model in order to get input from the user, it would be impossible to run a unit test against the view-model because there’s no way for a unit test to respond to the dialog. In addition, MVVM promotes better workflow between designers and developers. Designers can wire up actions to elements straight from the XAML. Some code-behind may still be necessary, but it should be restricted to view-related activities, such as starting and stopping animations or communicating with the user via messages or dialogs.
In response to these arguments I’ve heard it said, “We don’t have designers, and I don’t care about testability or maintainability.” In this case, the answer is simple: don’t use MVVM! If all you care about is quickly building an app, then just use code-behind and forget about MVVM, even if everyone else is doing it. Better not to use MVVM than sabotage it by taking shortcuts.
OK, so let’s say I’ve convinced you not to display dialogs from the view-model. How do you go about obtaining information from the user from a method that resides in the view-model? One approach is to define a dialog service interface. The problem I have with this method is that it feels too tightly coupled to me. This is typical of interfaces in general. The view-model needs to hold onto a reference to the object implementing the interface, and classes must implement all members of the interface even the ones they may not care about. Another approach is to use a message bus to handle all communication between view and view-model. This feels to loosely couples to me. I can appreciate the need for a mediator to pass messages between various view-models (and I plan to add this feature to my toolkit), but the view already holds a reference to the view so decoupling them seems pointless to me.
In my Simple MVVM Toolkit I advocate what I like to call “goldie locks” coupling: not too tight, not too loose. The answer is to use an event-based communication model. Events have finer granularity than interfaces because subscribers can pick and choose which events to subscribe to, and the publisher is relieved from the burden of maintaining direct references to subscribers. In addition, the view-model is not aware that it’s displaying a dialog – it just needs some data from somewhere and doesn’t care who provides it or how it’s obtained. Using events also alleviates the need for an intermediary such as a message bus. Because the view already has a reference to a view-model, it can easily subscribe to events and specify a callback method. In the callback the view can show information to the user and get a response in any way it wants.
Let’s say you want to notify the user if there is an exception in your view-model. You may want to display a message box or set the content of a label control on the view. Doing so directly from the view-model would violate the separation of concern and tightly couple the view to the view-model, at the same time making the view-model untestable. To inform the view that you want to notify it of an error all you have to do is expose an event on your view-model and let the view subscribe to it. It’s customary to use the built-in EventHander<T> delegate type, where T is a class that extends EventArgs. My toolkit has a NotificationEventArgs type for just this purpose. It has a single Message property of type string.
public class NotificationEventArgs : EventArgs
{
public string Message { get; protected set; }
}
There is also a generic version of NotificationEventArgs that allows you to send along an outgoing payload, which the subscriber can consume.
public class NotificationEventArgs<TOutgoing> : NotificationEventArgs
{
public TOutgoing Data { get; protected set; }
}
Your view-model simply exposes an event as an EventHandler<NotificationEventArgs>, then fires the event when it wants to communicate with external parties, such as a view or unit test.
public class ProductListViewModel
{
public event EventHandler<NotificationEventArgs
<Exception>> ErrorNotice
private void ProductsLoaded(List<Product> entities,
Exception error)
{
if (error != null && ErrorNotice != null)
{
ErrorNotice(this, new NotificationEventArgs<Exception>
("Unable to retrieve products", error));
else
Products = entities;
}
}
}
The view can subscribe to the ErrorNotice event with a method that, for example, shows a message box or dialog of some sort.
public partial class ProductListView : UserControl
{
ProductListViewModel model;
public ProductListView()
{
InitializeComponent();
// Get model from data context
model = (ProductListViewModel)DataContext;
// Subscribe to notifications from the model
model.ErrorNotice += OnErrorNotice;
}
void OnErrorNotice(object sender,
NotificationEventArgs<Exception> e)
{
// Show user message box
MessageBox.Show(e.Message, "Error", MessageBoxButton.OK);
// Trace error information
Debug.WriteLine(e.Data.ToString());
}
}
This shows how the view-model can communication information to the view in a loosely coupled manner. But what if you need to obtain information from the user that the view-model needs in order to proceed? The way to accomplish this is with a callback parameter in NotificationEventArgs where you can process the user response, which could be anything from a boolean (for example in the case of a delete confirmation) to the result of a child window. This plays nice with the asynchronous nature of dialogs in Silverlight, which are not truly model as they are in WPF or Windows Forms (this is because you can’t rely on the Windows message pump in a cross-platform framework such as Silverlight).
Here is an example of a event fired by the view-model to indicate that a particular product is available for order. It exposes an event with a NotificationEventArgs that takes two type arguments, both boolean, for outgoing and incoming data. The view-model needs to send a boolean value to the view indicating whether or not the product is available, and the view needs to reply with a boolean value indicating whether the user wishes to order the product.
public class ProductListViewModel
: ViewModelBase<ProductListViewModel>
{
public event EventHandler<NotificationEventArgs<bool, bool>>
ProductAvailableNotice;
private void ProductAvailable(bool available)
{
// Notify view that product is available
if (ProductAvailableNotice != null)
{
ProductAvailableNotice(this,
new NotificationEventArgs<bool, bool>
(null, available, PlaceOrder));
}
}
private void PlaceOrder(bool confirm)
{
if (confirm) serviceAgent.OrderProduct();
}
}
This version of NotificationEventArgs also has a Completed event, which is an Action<TIncoming> and can be set in the constructor. That’s the secret sauce of allowing the view to communicate a response back to the view-model, thus enabling two-way communication between the two. In this example, we are calling OrderProduct on the service agent if we received a confirmation from the user.
public class NotificationEventArgs<TOutgoing, TIncoming>
: NotificationEventArgs<TOutgoing>
{
// Completion callback
public Action<TIncoming> Completed { get; protected set; }
}
In this case we wish to prompt the user by displaying a ChildWindow with a message that shows the product name and asks the user to click either the Yes or No button. After receiving a response, we will execute the callback passed to the view in the NotificationEventArgs parameter.
void OnProductAvailableNotice(object sender,
NotificationEventArgs<bool, bool> e)
{
// Product is available
if (e.Data)
{
// Show OrderProductView
OrderProductView orderProductView = new OrderProductView();
var orderProductVm = (OrderProductViewModel)
orderProductView.DataContext;
orderProductVm.Product = model.SelectedProduct;
orderProductView.Closed += (s, ea) =>
{
if (orderProductView.DialogResult == true)
{
// Notify view model to order product
e.Completed(true);
}
};
orderProductView.Show();
}
else
{
MessageBox.Show("Product is unavailable.",
"Product Availability", MessageBoxButton.OK);
}
}
Notice how we invoke the event args Completed event in the Closed event of the dialog if the user clicked the Yes button, which sets the DialogResult of the ChildWindow to true. We now have full two-way loosely-coupled communication based on events. Using events keeps the view-model UI-agnostic and fully testable. And because views and view-models are generally paired, there’s no need in this case for an event mediator or message bus.
For the full source code to this example, just download the latest version of my Simple MVVM Toolkit and open up the solution in the SimpleMvvm-Main folder in the Samples directory.