Introduction
This article presents an MVVM conform implementation of an application that demonstrates how:
- Application Startup/Shutdown and
- Window Open/Close
can be implemented.
I have researched different solutions for closing windows in WPF for a while and I found a number of approaches (which were at first confusing). There are suggestions on the Internet [2] where an attached behaviour in the MainWindow
of an application is used to close a window via an attached property [3] which is bound to a ViewModel
. My sample application takes advantage of that solution and adds on to it. The sample implements:
- Closing an Application's
MainWindow
(it makes no difference whether this is initiated by the MainWindow
, the ViewModel
, or the Windows Operating System - the App
object is always in full control of the situation) - A dialog specific
viewmodel
that can be used to drive a dialog and receive a DialogResult
The dialog viewModel
also sports a facility for evaluating user input and producing corresponding messages (information, warning, error, etc.) if some input is not of the expected quality.
Using the Code
Application Startup and Shutdown
The sequence for the application start-up is to:
- Instantiate an object of the
App
class (which is generated by default in a WPF project) and use a Application_Startup
method in that object to:
- Instantiate an
AppViewModel
(sometimes also referred to as workspace) - Instantiate an application
MainWindow
- Attach the
ViewModel
to the DataContext
of the MainWindow
- and
Show
the MainWindow
private void Application_Startup(object sender, StartupEventArgs e)
{
AppViewModel tVM = new AppViewModel();
this.win = new MainWindow();
this.win.Closing += this.OnClosing;
tVM.RequestClose += delegate
{
if (this.mRequestClose == false)
{
this.mRequestClose = true;
this.OnClosed(this.win.DataContext as ViewModel.AppViewModel, this.win);
}
};
this.win.DataContext = tVM;
this.InitMainWindowCommandBinding(this.win);
this.win.Show();
}
This startup sequence has the advantage that View
and ViewModel
have no references of each other but can still work together perfectly. This is especially advantageous if you have to maintain more than one MainWindow
and you have to instantiate a different MainWindow
based on the currently used configuration or a command line option. Command line parameters can also be evaluated in the Application_Startup
method. The method can then prepare the AppViewModel
accordingly, and start-up the application in the expected manner (evaluating command line parameters is not implemented in the sample though).
The App
class represents an outer shell that is wrapped around the instances of View
s and ViewModel
s of an application. That outer shell should becomes active whenever the application starts up or shuts down.
The application shut down sequence is the inverse of the start-up sequence. That is, the MainWindow
tells the AppViewModel
that it has received a request for closing the application, the ViewModel
evaluates this based on the current situation and either rejects it or confirms the request.
Whether the shutdown sequence can take place or not is routed through the App
class:
this.win.Closing += this.OnClosing;
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = this.OnSessionEnding();
}
private bool OnSessionEnding()
{
ViewModel.AppViewModel tVM = this.MainWindow.DataContext as ViewModel.AppViewModel;
if (tVM != null)
{
if (tVM.IsReadyToClose == false)
{
MessageBox.Show("Application is not ready to exit.\n" +
"Hint: Check the checkbox in the MainWindow before exiting the application.",
"Cannot exit application", MessageBoxButton.OK);
return !tVM.IsReadyToClose;
}
tVM.OnRequestClose(false);
return !tVM.IsReadyToClose;
}
return true;
}
This cascade of events takes place when the user clicks the MainWindow
close button. It is slightly different when the user selects the File>Exit menu option or presses the ALT+F4 Keys. In this case, the routed command binding in App.xaml.cs is executed:
win.CommandBindings.Add(new CommandBinding(AppCommand.Exit,
(s, e) =>
{
e.Handled = true;
((AppViewModel)win.DataContext).ExitExecuted();
}));
...which invokes the ExitExecuted()
method in the AppViewModel
class:
if (this.mShutDownInProgress == false)
{
this.mShutDownInProgress = true;
if (this.OnSessionEnding != null)
{
if (this.OnSessionEnding() == true)
{
this.mShutDownInProgress = false;
return;
}
}
this.WindowCloseResult = true;
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
...the above code calls the OnSessionEnding
method in the App
class via the delegate method property of the same name. This is done in such a convoluted way because I want to make sure that all shut down requests are handled by one method (App.OnSessionEnding()
).
The shutdown progresses by setting the this.WindowCloseResult = true;
which in turn invokes the attached DialogCloser
property, which in turn will closes the MainWindow
via the bound boolean property (see MainWindow.xaml).
There is also a third path of execution towards the shutdown of the application, which is to shutdown the Windows Operating system. This event iterates through each application and asks them whether they can shutdown or not. We have set the SessionEnding="App_SessionEnding"
property in the App.xaml code to invoke the OnSessionEnding
method when Windows shuts down. This way, Windows will no longer shut down if the OnSessionEnding
method signals that we still have things to save on disk (unless user forces Windows shut down of course).
To summarize this section. There are several execution paths for shutting down the main window, and through that, the application. All these paths are based on the same evaluation function (OnSessionEnding
in App
class) and end in one shutdown function OnClosed
to make sure that session data can be saved before the application is down for good.
- The main window can send the
Closing
event to check whether close down is OK via OnClosing
method in the App
class. - The main window can execute the
AppCommand.Exit
routed command (via File>Exit or Alt+F4) to shutdown the application through the ExitExecuted()
method in the AppViewModel
class. - The Windows operating system can invoke the
App_SessionEnding
method in the App
class to gracefully shutdown our application
Dialog Open and Close
The previous section has described one case in which the attached property DialogCloser
is used to close the MainWindow
through setting the WindowCloseResult
property to true
. This attached behaviour (AB) is actually designed such that it can be used like a DialogResult
. It is null
by default and is set to true
or false
when the user closes the application via Cancel or OK.
I am using this behaviour to implement a viewmodel
that drives a dialog (or any other view) in an MVVM conform way such that the dialog will not close unless the user has input data of the expected quality. The important theme here is that we have a viewmodel
which drives the view through its complete life cycle without using any code behind. To do this, we have to construct a ViewModel
and View
object, attach the former to the DataContext
of the later and subscribe to the Closing
event of the view. Further more, View
and ViewModel
should implement an OK
and Cancel
Command to do the corresponding processing when the user clicks either button.
That seems to be a lot of work - so we have to do this for every ViewModel
that drives a dialog? Too painful, I hear you saying. Too convoluted some already said [2] claiming that code behind is OK in some cases. Some of you have a point there but if the complexity could by abstracted away into a neat little class which then could be used in a viewmodel
whenever a dialog (or any other view) is needed to process input based on a OK
or Cancel
?
The DialogViewModelBase
class implements the above items (and a bit more) needed to drive a dialog through its life cycle. The class can be used to make user input validation a trivial task.
This dialog is shown through the ShowDialog
routed command in the AppCommand
class, which is bound to the ShowDialog
method in the AppViewModel
class. This method invokes the ShowDialog
method in the Util
class. Here, we instantiate the dialog, attach a copy of the UsernameViewModel
object and initiate the OpenCloseView
property.
The UsernameViewModel
class instantiates the DialogViewModel
class in its OpenCloseView
property. The OpenCloseView
property is bound in the UsernameDialog.xaml.
So, when you click OK or Cancel (or use the Escape key) in the UsernameDialog
, then you are actually executing a corresponding command in the DialogViewModel
class. Now, executing the OK command invokes the PerformInputDataEvaluation
method, which in turn, invokes an external method (ValidateData
) bound to the EvaluateInputData
delegate property.
The signature of that method is to return true
or false
depending on whether there are problems in the user input or not. If there are problems, these can be detailed, classified, and returned with Msg
objects in the out List<msg>
parameter. This list of messages is then fed into the ListMessages
property of the ObservableCollection<msg>
type. These messages are then visible with icons because the XAML of the dialog makes use of the CountToVisibilityHiddenConverter
to hide the messages StackPanel
when there are no messages. And it also uses the MsgTypeToResourceConverter
to convert the category of a message into an image resource that can be displayed in the list of messages [4].
This input evaluation and message listing can be disabled by simple setting the EvaluateInputData
delegate property to null
. The dialog will then close via ViewModel
when the IsReadyToClose
is true
.
Part 2 Using a DialogService
I received controversial feedback when I published the first version of this article. Some people were missing a dialog service implementation (I get to that in this section) and others were saying this approach of implementing dialogs is an over-design. Looking back on it, and having seen some other articles on similar topics [5], - I have realized that I stumbled into a "CodeProject trap" that snaps shut because some topics, such as, MVVM, are not so well defined and some patterns are quite different when being applied to solve different requirements.
My initial idea was to simply publish a small code example that could be used to implement simple WPF applications. Although, the requirements:
- unit test
- code re-use, and
- separation of concerns
[6] are more than useful problems to solve, they were not part of my initial concerns. I have reviewed a lot of articles on MSDN, CodeProject, and elsewhere since then and have come to the conclusion that a dialog service is a more than a useful exercise.
The key-terms to know are "Inversion of Control" (IOC) and Dependency Injection (DI). Just enter "IOC", "Dependency Injection", or "DialogService" into the search engine here at CodeProject or elsewhere and you will find numerous explanations on that topic.
I followed the IOC track and found there are lots of frameworks on that topic: Unity, MEF, StructureMap, Castle.Windsor, AutoFac, Chinch, LinFu, Hiro, and Ninject, which explains why people are so tired of being shown yet another framework " />
I am not going to implement and document yet another framework in this article. Instead, we are going to look at a simplified implementation of a DialogService
to explain and document the IOC service pattern with a sample implementation. I found such a sample implementation here at CodeProject [5], simplified it a little bit more, and applied it to my sample code as described next.
Disore's original implementation [5] implements a few services to define interfaces for:
- Reading
Person
information from XML - Displaying
MessageBox
contents - Using common dialogs such as Open File or Folder Browsing, and
- Using
ViewModel
s with a DialogService
I was interested in the last point 'Using ViewModel
s with a "DialogService
", only. So, I downloaded his source code and removed everything that was unnecessary for the DialogService
implementation. This left me with the classes in the "Service" and "WindowViewModelMapping" folder:
We like simple things, right? It turns out that using these classes for the first time is simple (thanks to Disore's article and source code). But understanding their correct application in a non-intimidating way is all but simple. So, let's walk through the code to see what we can learn here.
The ServiceLocator
class is static
which means that it is initialized and loaded as soon as the .NET Framework loads the corresponding namespace. The dialog service starts its timely existence in the App.Application_Startup
of the App
class. The following lines:
ServiceLocator.RegisterSingleton<IDialogService, DialogService>();
ServiceLocator.RegisterSingleton<IWindowViewModelMappings, WindowViewModelMappings>();
initialize two services:
- A
DialogService
- which is used to create and show a View
for a ViewModel
, and - A
WindowViewModelMappings
service to associate (map) a ViewModel
class with its corresponding View
class.
We can register these services in any way we would like but we can (obviously) not instantiate a View
for a ViewModel
unless there is a map (association) for both classes. Disore [5] shows different ways for creating this map in his article. In our case, though, we use the below map...
public WindowViewModelMappings()
{
mappings = new Dictionary<type,>
{
{ typeof(UsernameViewModel), typeof(UsernameDialog)}
};
}
...to associate the UsernameViewModel
class with the UsernameDialog
class at run-time. This comes to life in the below AppViewModel
function:
public void ShowUserNameDialog()
{
UsernameViewModel dlgVM = null;
try
{
dlgVM = new UsernameViewModel(this.mTestDialogViewModel);
dlgVM.InitDialogInputData();
dialogService.ShowDialog(this, dlgVM);
if (dlgVM.OpenCloseView.WindowCloseResult == true)
{
Console.WriteLine("Dialog was OK'ed.");
this.mTestDialogViewModel.FirstName = dlgVM.FirstName;
this.mTestDialogViewModel.LastName = dlgVM.LastName;
}
else
Console.WriteLine("Dialog was Cancel'ed.");
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString());
}
}
The call to InitDialogInputData()
could be abstracted away into the interface of the dialog service but I am not going into that because you feel comfortable with this concept, you should just use one of the 10 IOC containers listed above.
Summarizing the concept - there is one static
class, the ServiceLocator
[7] which is used to register the DialogService
class and the WindowViewModelMappings
class. The result of that registration is stored in a dictionary
object in the ServiceLocator
class:
private static Dictionary<Type, ServiceInfo> services = new Dictionary<Type, ServiceInfo>();
The call to the dialogService
dialogService.ShowDialog(this, dlgVM);
then pulls out the mapping service from the ServiceLocator
dictionary which in turn pulls out the corresponding view from the WindowViewModelMappings
dictionary:
public bool? ShowDialog(object ownerViewModel, object viewModel)
{
Type dialogType = windowViewModelMappings.GetWindowTypeFromViewModelType(viewModel.GetType());
return ShowDialog(ownerViewModel, viewModel, dialogType);
}
Now we have an implementation that makes sure that View
s and ViewModel
s are not aware of each other. That's good for unit testing - which turns out to be simpler than you think. Just get, for example, Disore's source code, download and install the NUnit setup and use the Graphical GUI to get started with unit testing in 5 minutes.
Using an implementation like this uncovers other possibilities as well. Consider, for example, an application with an advanced and a basic view, - or an application in which you need a viewing mode or an editing mode. You could simply implement such run-time changes by mapping/re-mapping views at run-time.
Points of Interest
I have learned to use delegate methods to execute a flexible piece of code within a complex system, exposing thus, exactly what I need to expose while keeping the complexity of the overall system hidden. I also learned that common functions, such as, driving a dialog through its lifecycle can be encapsulated in a class, which can be instantiated when that dialog (or view) is being shown. Using a ServiceLocator
can drive this development even further towards a professional implementation. I now have a pattern that I can simply apply without having to care about Window Close, Closing, or any other events, because I could just implement:
- the
OpenCloseView
property, the Input Evaluation method, and the ShowDialog
method - plus the required
XAML
and I am done with another dialog. Oh, and all that is done in an MVVM conform fashion. Now why would that not be a good approach?
References
History
- 30th June, 2012: Initial version
- 3rd July, 2012 (fixed a bug in
OnClosing
method and moved util.ShowDialog
out of ViewModel
namespace) - 9th August, 2012: Added
DialogService
implementation section and advanced code sample