how to apply a viewmodel to new windows
A colleague asked me a question today. How do you apply the Model View ViewModel pattern to new Windows and Popups. To illustrate that, i wrote a small sample app, which I wanted to share with you.
This post is part of a series:
<Back to ‘How do I adapt a simple vs complex viewModel’>
<Forward to ‘How to trigger animations from your ViewModel’>
There are usually two types of windows you might want to open. Modal windows, where the current window is no longer accessible until the modal window is closed, and non modal popups, where both the current window and the popup are opened and usable at the same time.
Modal windows
From the perspective of the ViewModel, opening another viewmodel in a popup is just like a very slow synchronous service. Take for example opening a confirmation dialog. The viewmodel doesn’t care if you ask the confirmation from a user, or from an external service. All it knows is that it needs the confirmation and has to wait until it get’s it.
The following codesnippet shows this:
1: public class MainWindowViewModel : INotifyPropertyChanged
2: {
3: //...
4:
5: // At a certain point in time, the MainViewModel want's to open a modal window
6: // pass it the number of the window and know the number of the window that was closed last.
7: private void OpenDialogWindow()
8: {
9: // Create the viewmodel for the child window. This allows us to communicate
10: // with the window in the popup
11: var viewModel = new ChildViewModel();
12:
13: // pass the viewmodel some data: in this case, the number of the current window
14: viewModel.WindowNumber = ++WindowCount;
15:
16: // opening a modal dialog is like a synchronous service
17: _windowService.ShowViewModelInDialog<ChildView>(viewModel);
18:
19: // After the window is closed, get the number of the window that was just closed
20: LastWindowToBeClosed = viewModel.WindowNumber;
21: }
22: }
You can see how the viewmodel askes a service (The WindowService) to show a particular viewmodel. To keep the implementation of the WindowService, i’m also passing the Template (ChildView) to use to visualize the View. But there are many different ways of solving this, for example the one I’ve demonstrated in my Outlook style sample.
The code for the WindowService is very simple:
1: public class WindowService : IWindowService
2: {
3: public bool? ShowViewModelInDialog<TViewType>(object viewModel)
4: where TViewType : Control, new()
5: {
6: Window w = new Window();
7: TViewType view = new TViewType();
8: view.DataContext = viewModel;
9: w.Content = view;
10: return w.ShowDialog();
11: }
12: }
This is simply a generic way of showing a viewmodel in a window, by using a View to visualize the ViewModel and show the window as a dialog.
Non modal popups
Now in the case of a normal popup, it’s more like an asynchronous service. The code in the ViewModel can continue to execute, but the opened window can raise events to signal status changes. This is very similar to, for example, an asynchronous webservice, that raises events for progress changes or completion.
The following code snippet shows this:
1: private void OpenPopupWindow()
2: {
3: // Create the ViewModel to communicate with the service
4: var viewModel = new ChildViewModel();
5:
6: // Pass the ViewModel some information
7: viewModel.WindowNumber = ++WindowCount;
8:
9: // Opening a popup is like an asynchronous service. You can listen to change notification events
10: // such as the Closed Event i've implemented
11: _windowService.ShowViewModelInPopup<ChildView>(viewModel);
12:
13: // Listen for a change notification, when the window is closed
14: viewModel.Closed += viewModel_Closed;
15: }
16:
17: // This method is called when the window is closed again
18: void viewModel_Closed(object sender, EventArgs e)
19: {
20: // Get the information from the viewmodel
21: var viewModel = (sender as ChildViewModel);
22: this.LastWindowToBeClosed = viewModel.WindowNumber;
23:
24: // Unsubscribe from the event to prevent memory leaks
25: viewModel.Closed -= viewModel_Closed;
26: }
As you can see, the code is similar to the Modal example. The only difference is, that you have to sign up for an event to get the required information.
The WindowService is slightly more complicated in this case:
1: public void ShowViewModelInPopup<TViewType>(object viewModel)
2: where TViewType : Control, new()
3: {
4: Window window = new Window();
5: TViewType view = new TViewType();
6: view.DataContext = viewModel;
7: window.Content = view;
8:
9: // If the viewmodel knows about window closing events (IClosedAware)
10: // then forward closing notifications to the viewModel;
11: SubScribeToClosedEvent(window, viewModel as IClosedAware);
12:
13: window.Show();
14: }
15:
16: private void SubScribeToClosedEvent(Window window, IClosedAware view)
17: {
18: if (view == null)
19: return;
20:
21: window.Closed += (sender, args) => view.OnClosed();
22: }
I wanted a generic way for the WindowService to notify a viewmodel if the window is closed. So I created the IClosedAware interface, and implemented that on the ViewModel. The WindowService will, if the viewModel implements the IClosedAware, sign up for the ClosedEvent of the window and notify the ViewModel if that event occurs.
Hopefully this explains the way you can work with several Windows when using MVVM.
Comments
- Anonymous
December 07, 2010
Hi, I have just been reading thisarticle in your blog, "how to apply a viewmodel to new windows" and I have a question about this I hope you can answer. What is the best way to set the window title and a handful of other properties? I have done it as follows in the code behind file for the view: private void CustomFilter_Loaded(object sender, RoutedEventArgs e) { Window parent = this.Parent as Window; parent.Title = "Custom Filter"; parent.SizeToContent = SizeToContent.WidthAndHeight; parent.ResizeMode = ResizeMode.NoResize; } Thanks for any help you can offer, I am assuming this will be forwarded to you. ChangedDaily