This article is compatible with the latest version of Silverlight.
Programming Model-View-ViewModel applications with Silverlight is not always an easy task. In many cases there are some architectural obstacles that prevent from correctly applying the pattern. The ChildWindow control is affected by this problem because it cannot be opened or closed through a property bound to the ViewModel. In this article I will show how to use the EventAggregator and a Unity Service with Prism v2 to decouple the ChildWindows from the ViewModels and let the MVVM architecture to work effectively.
Download source code
Introduction
Popups, overlays, and windows are common patterns to display and collect information to or from an user in an application. In the past, this kind of elements have been deprecated by the web application model because they are inconsistent and difficult to handle in a simple HTML page, but with Dynamic HTML, Ajax and recently with Silverlight they become again an option while writing an application.
Silverlight 3.0 has introduced a new control - the ChildWindow - that lets the developer easily create effective popup windows, with a model very close to what we may experience in programming a Windows Forms application. A ChildWindow is quite similar to a UserControl, with a XAML file and a code-behind, but has a subtle difference. It does not need to be inserted into the Visual Tree of a page but may be instantiated and opened from the code-behind on top of what we are currently showing. Here is a sample code showing how to open a ChildWindow.
MyChildWindow window = new MyChildWindow();
window.Closed += new EventHandler(window_Closed);
window.Show();
Popping up a ChildWindow in this way is comfortable while you are writing a normal application - using the code-behind - but it is very annoying if you are implementing the Model-View-ViewModel pattern. Calling a method to open the window and waiting an event to be raised to know if it has been closed, is a model that goes on the opposite direction of what you expect to do in a MVVM application.
If you put the call in the code-behind you are violating one of the fundamental precepts of the pattern because you should put the interface logic in the ViewModel and leave the View clean to let the Designers make them work without knowing about the code to open the popup. The code-behind is strongly connected to the View and then it is not a good place for the purpose.
It may seem that a good place for doing the call to open a popup is the ViewModel but if you put the code in the ViewModel you are strongly connecting the UI to the Logic and from a testing perspective you are doing something that you will be unable to test.
While opening a popup requires a call to a method, there is not a valid solution to do it without creating a flaw in the MVVM pattern and coupling what is expected to be separated. So probably the best solution is to take away the logic required to open the popup and create a service responsible to manage the popup from the moment it is opened to the moment it has been closed. You just have to raise an event and the service receives it and opens the new ChildWindow.
The three cases of a popup
What I have suggested may seem to be simple but if you analyze more deeply the solution you will quickly understand that there are different conditions to handle. In my experience when you decide to pop up a window you are in one of those three cases:
a) You need to give a feedback about an action, an error or something similar. You may also popup a window to do more complex operations, but in this case the popup is something you can forget after you have popped it out. The window shows a message, lets the user do something, and you do not need to know anything about when the user has closed it.
b) You are requesting a confirmation. This is the case of a simple window asking about the deletion of a record. In this case you need to know the answer of the user but the answer is specific to the calling code. No one needs to know about the deletion, but the calling ViewModel that has to delete the record.
c) You have to collect information that needs to be handled by multiple ViewModels. An example is a search window where you ask the search keys and then you can have a View showing the keys, one showing the results and so on. When the user hits the OK button the collected data has to be spread across all the viewmodels.
In the next paragraph I will show you how I solve these cases in my real world applications. The solutions do not pretend to be final, but are what lets me be productive and have a good implementation of the MVVM pattern.
Silverlight, Prism and Services
In a previous paragraph, I've anticipated that the solution needs to create a Service responsible of handling one or more popups. With the word "service" I identify a simple class that implements the Singleton pattern. This class is unique across the Application Domain.It is instantiated at the start of the application and remains idle listening for incoming events.
In my applications I'm used to adopting the Composite Application Guidance for Silverlight, also known as Prism. The library, originally published on codeplex (http://www.codeplex.com/prism), is now available to download on MSDN here. The library is open source and is currently developed by the Microsoft Pattern & Practices team.
In Prism there are two things that may help you create the Service and communicate with it. The first is Unity - a simple and effective IoC container responsible of managing instances and lifetimes. With Unity you can register a class and give it a LifetimeManager to handle when the class is created and destroyed. Using a ContainerControlledLifetimeManager transforms a class in a Singleton service.
To register a Service you have to start the Unity container using a Bootstrapper. The Bootstrapper class is instantiated and started in the App.xaml.cs:
private void Application_Startup(object sender, StartupEventArgs e)
{
new Bootstrapper().Run();
}
In a simple application, the Bootstrapper is a good place to initialize the services. You need to take a reference to the IoC container - it is a Service itself and is instantiated when you run the Bootstrapper - and register an instance of the class that you want to run as a service. It is important here to register an instance and not a type because we need the Service to immediately start its lifetime:
/// <summary>
/// Initializes the modules.
/// </summary>
protected override void InitializeModules()
{
// get the IoC container
this.TheContainer =
ServiceLocator.Current.GetInstance<IUnityContainer>();
// Register the service
this.TheContainer.RegisterInstance(
typeof(DialogManager),
new DialogManager(),
new ContainerControlledLifetimeManager());
base.InitializeModules();
}
When the Service has been registered and then started, it has to listen for events raised for the ViewModels. Here Prism helps you with another useful tool called EventAggregator. The EventAggregator is another service that is responsible for taking incoming events from the various ViewModels and forwarding them to other listening ViewModels or services. Here, an actor subscribes the event and another one publishes it attaching a payload to it that will be received by the subscriber. So for opening a window someone has to write this code:
public class ErrorEvent : CompositePresentationEvent<Exception>
{ }
// ... then ...
this.TheAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
this.TheAggregator.GetEvent<ErrorEvent>().Publish(ex);
The ErrorEvent class represents the event to someone that can subscribe. It derives from the CompositePresentationEvent class and has a payload of type Exception. When we catch an exception we can simply raise the event in a way to popup an error dialog.
Handling the three cases
After publishing the event, we expect that it is routed to the listening service. The service subscribes the event and then it opens the popup. The code I've proposed in the last box refers to the simplest of the cases, when the popup does not need to return anything to the caller. In this case the service simply opens the popup:
public DialogManager()
{
this.TheAggregator =
ServiceLocator.Current.GetInstance<IEventAggregator>();
this.TheAggregator.GetEvent<ErrorEvent>()
.Subscribe(ShowErrorDialog);
}
public void ShowErrorDialog(Exception ex)
{
ErrorDialog dialog = new ErrorDialog();
dialog.Message = ex.ToString();
dialog.Show();
}
When I need to receive a simple response (simple as yes or no), I need to use only in the calling ViewModel. I'm used to passing a delegate to the service it will callback. This may be a bit complex to understand but I found it is useful to leave the code simple and compact thanks to lambda expressions. The problem here is that the EventAggregator does not have a way to answer only to the publisher of the event so I work around this limitation by passing a reference to a method which the service will call:
// raise the event and handle the answer
this.TheAggregator.GetEvent<ConfirmRequestEvent>()
.Publish(
new ConfirmMessage("Do you confirm?",
r => // <=== this is an Action<bool?> delegate
{
if (r == true)
{ /* user confirmed */ }
}));
// handle the service
public void ShowConfirmDialog(ConfirmMessage message)
{
ConfirmDialog dialog = new ConfirmDialog();
dialog.Message = message.Message;
dialog.Closed +=
(s, e) => { message.Callback(dialog.DialogResult); };
dialog.Show();
}
Here, the payload is a class that transports the message to display and an Action<bool?> that is the callback method to call. By using a lambda expression the code remains very compact.
Finally, when you need to have a result spread to all the listening ViewModels you can simply raise an event from inside the service and it will be handled from every subscriber. In my example, I simulate a color selector window where you can choose a color to give to the user interface. Here is the service method:
public void ShowColorSelectorDialog(object dummy)
{
ColorSelectorDialog dialog = new ColorSelectorDialog();
ColorSelectorViewModel vm = new ColorSelectorViewModel();
dialog.DataContext = vm;
dialog.Closed +=
(s, e) =>
{
if (dialog.DialogResult == true)
{
this.TheAggregator.GetEvent<SetColorEvent>()
.Publish(new SetColorMessage(vm.Number, vm.Color));
}
};
dialog.Show();
}
In this snippet I assign a ViewModel to the View because the window needs a kind of logic (more complex that a simple "yes or no") to handle the user interaction. When the user closes the window I take the result from the ViewModel and not from the View because in my opinion this is a responsibility of this class.
Conclusion
Like every MVVM application there is a lot of code to write to make them work and it is difficult to show every line of code in an article. This is the reason why I've included a working sample where you can see all of the three solutions implemented and working. If you have any questions do not hesitate to contact me on my weblog.
Andrea Boschin
Most Valuable Professional - Silverlight
Freelance Consultant & Contractor
http://www.silverlightplayground.org