Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

Closing Windows and Applications with WPF and MVVM

Rate me:
Please Sign up or sign in to vote.
4.68/5 (13 votes)
10 Aug 2012CPOL12 min read 221.5K   5.8K   66   36
This article samples a MVVM conform implementation of startup and shutdown sequences for an application and its dialogs.

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
C#
private void Application_Startup(object sender, StartupEventArgs e)
{
  AppViewModel tVM = new AppViewModel();  // Construct ViewModel and MainWindow
  this.win = new MainWindow();

  this.win.Closing += this.OnClosing;

  // When the ViewModel asks to be closed, it closes the window via attached behaviour.
  // We use this event to shut down the remaining parts of the application
  tVM.RequestClose += delegate
  {
    // Make sure close down event is processed only once
    if (this.mRequestClose == false)
    {
      this.mRequestClose = true;

      // Save session data and close application
      this.OnClosed(this.win.DataContext as ViewModel.AppViewModel, this.win);
    }
  };

  this.win.DataContext = tVM; // Attach ViewModel to DataContext of ViewModel
  this.InitMainWindowCommandBinding(this.win);  // Initialize RoutedCommand bindings
  this.win.Show(); // SHOW ME DA WINDOW!
}

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 Views and ViewModels 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:

C#
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; // Cancel close down request if ViewModel is not ready, yet
    }
 
    tVM.OnRequestClose(false);
 
    return !tVM.IsReadyToClose; // Cancel close down request if ViewModel is not ready, yet
  }
 
  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:

C#
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:

C#
if (this.mShutDownInProgress == false)
{
  this.mShutDownInProgress = true;

  if (this.OnSessionEnding != null)
  {
    if (this.OnSessionEnding() == true)
    {
      this.mShutDownInProgress = false;
      return;
    }
  }

  this.WindowCloseResult = true;              // Close the MainWindow and tell outside world
                                             // that we are closed down.
  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).

Image 1

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.

Image 2

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.

Image 3

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 Smile | <img src= " />

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 ViewModels with a DialogService

I was interested in the last point 'Using ViewModels 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:

Image 5

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:

C#
// Configure service locator
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...

C#
public WindowViewModelMappings()
{
  mappings = new Dictionary<type,>
  {
////{ typeof(AppViewModel), typeof(string) } ////,
{ 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:

C#
public void ShowUserNameDialog()
{
  UsernameViewModel dlgVM = null;

  try
  {
    dlgVM = new UsernameViewModel(this.mTestDialogViewModel);

    // It is important to either:
    // 1> Use the InitDialogInputData method here or
    // 2> Reset the WindowCloseResult=null property
    // because otherwise ShowDialog will not work twice
    // (Symptom: The dialog is closed immediately by the attached behaviour)
    dlgVM.InitDialogInputData();

    // Showing the dialog, alternative 1.
    // Showing a specified dialog. This doesn't require any form of mapping using 
    // IWindowViewModelMappings.
    dialogService.ShowDialog(this, dlgVM);

    // Copy input if user OK'ed it. This could also be done by a method, 
    // equality operator, or copy constructor
    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:

C#
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:

C#
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 Views and ViewModels 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
The Windows Presentation Foundation (WPF) and C# are among my favorites and so I developed Edi

and a few other projects on GitHub. I am normally an algorithms and structure type but WPF has such interesting UI sides that I cannot help myself but get into it.

https://de.linkedin.com/in/dirkbahle

Comments and Discussions

 
GeneralMy vote of 5 Pin
MD-1122-Mar-15 22:13
MD-1122-Mar-15 22:13 
GeneralRe: My vote of 5 Pin
Dirk Bahle22-Mar-15 23:27
Dirk Bahle22-Mar-15 23:27 
QuestionDownload Code Pin
Jose Betances3-Nov-12 12:09
Jose Betances3-Nov-12 12:09 
AnswerRe: Download Code Pin
Dirk Bahle4-Nov-12 9:33
Dirk Bahle4-Nov-12 9:33 
GeneralGood article! Pin
Volynsky Alex9-Aug-12 21:33
professionalVolynsky Alex9-Aug-12 21:33 
GeneralRe: Good article! Pin
Dirk Bahle10-Aug-12 0:28
Dirk Bahle10-Aug-12 0:28 
GeneralRe: Good article! Pin
Volynsky Alex10-Aug-12 0:30
professionalVolynsky Alex10-Aug-12 0:30 
General[OT]Where does your "Convert XAML Vector Graphic to PNG" article go? Pin
Shuqian Ying16-Jul-12 17:34
Shuqian Ying16-Jul-12 17:34 
GeneralRe: [OT]Where does your "Convert XAML Vector Graphic to PNG" article go? Pin
Dirk Bahle17-Jul-12 3:41
Dirk Bahle17-Jul-12 3:41 
GeneralRe: [OT]Where does your "Convert XAML Vector Graphic to PNG" article go? Pin
Shuqian Ying17-Jul-12 9:33
Shuqian Ying17-Jul-12 9:33 
GeneralRe: [OT]Where does your "Convert XAML Vector Graphic to PNG" article go? Pin
Dirk Bahle17-Jul-12 12:09
Dirk Bahle17-Jul-12 12:09 
GeneralRe: [OT]Where does your "Convert XAML Vector Graphic to PNG" article go? Pin
Shuqian Ying17-Jul-12 13:15
Shuqian Ying17-Jul-12 13:15 
QuestionI have done this quite a bit in the past and just use a simple service Pin
Sacha Barber2-Jul-12 22:46
Sacha Barber2-Jul-12 22:46 
AnswerRe: I have done this quite a bit in the past and just use a simple service Pin
Dirk Bahle3-Jul-12 6:40
Dirk Bahle3-Jul-12 6:40 
GeneralRe: I have done this quite a bit in the past and just use a simple service Pin
Kevin Marois3-Jul-12 9:09
professionalKevin Marois3-Jul-12 9:09 
SuggestionOverdesign Pin
abdurahman ibn hattab2-Jul-12 21:20
abdurahman ibn hattab2-Jul-12 21:20 
GeneralRe: Overdesign Pin
Dirk Bahle3-Jul-12 5:56
Dirk Bahle3-Jul-12 5:56 
GeneralRe: Overdesign Pin
abdurahman ibn hattab3-Jul-12 7:01
abdurahman ibn hattab3-Jul-12 7:01 
QuestionHere's a REALLY Simple Solution Pin
Kevin Marois2-Jul-12 8:12
professionalKevin Marois2-Jul-12 8:12 
AnswerRe: Here's a REALLY Simple Solution Pin
Dirk Bahle2-Jul-12 8:58
Dirk Bahle2-Jul-12 8:58 
hm, I have tried this really simple solution but there must be something missing here,because when I execute GetWindowRef from my ViewModel as you requested - then
there is only one Window (the MainWindow) in the collection:

Application.Current.Windows

So, what am I missing? Are you suggesting to instantiate every Window and pull it
out with the GetWindowRef function? How about implementing a service locator
(<a href="http://www.codeproject.com/Articles/70223/Using-a-Service-Locator-to-Work-with-MessageBoxes">Using a Service Locator to Work with MessageBoxes in an MVVM Application</a>)

but is that not a little bit over the top for such a small code sample?
GeneralRe: Here's a REALLY Simple Solution Pin
Kevin Marois2-Jul-12 9:06
professionalKevin Marois2-Jul-12 9:06 
GeneralRe: Here's a REALLY Simple Solution Pin
Dirk Bahle2-Jul-12 10:23
Dirk Bahle2-Jul-12 10:23 
GeneralRe: Here's a REALLY Simple Solution Pin
Kevin Marois2-Jul-12 10:45
professionalKevin Marois2-Jul-12 10:45 
GeneralRe: Here's a REALLY Simple Solution Pin
Dirk Bahle3-Jul-12 7:26
Dirk Bahle3-Jul-12 7:26 
GeneralRe: Here's a REALLY Simple Solution Pin
Kevin Marois3-Jul-12 9:12
professionalKevin Marois3-Jul-12 9:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.