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).
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.
Andrew Scoppa made discussion of the course materials interesting and helped me to think of how it could apply to 'real world' examples. He also took time to answer questions as well as encourage talk about how the course materials were currently being applied (or not applied) to our company. The demos were very meaningful and appropriate to the coursework covered. Thanks very much! H. S.