Introduction
A common behavior when you work with WPF, MVVM, and SDI windows is that you can easily lose control of your windows when you close the main windows. That is, child windows remain open when you close the parent windows, and you need to close them individually.
To control this, you need to create a custom logic to close the child windows at
the right moment.
This article describes a method to do this, and offers a base class that implements control windows logic for you.
Background
The logic used here to control the windows is based on two properties in the base view model class. One property retains references to the parent window's view and view model, and the view and view model of the actual view model. This property is of the type ILinked
. See image.
The other it is a list of ILinked
objects with the same information, but from the child windows created for the actual view model.
The stored information in those properties gives you a chance to close all child windows when the parent window is closed and also to notify the parent windows when a child window is closed.
The two described properties are implemented in the LinkedViewModel
class, you should implement that your view model inherited from
LinkedViewModel
. This class has all you need to automatically
manage the child window states and to report to an existing parent about its self status.
Each time that a window is created, a list of ILinked
information about the possible children is created and LinkedValues
is populated with the necessary information about the window's parent and the information about the windows.
If you create a new window the new window creates it's proper LinkedValues
property and adds it to the child list of the parent.
All the logic of
the control is called in the constructors, and occurs in the following procedure:
private void SetParentValue(ContentControl view, ContentControl parentView,
ILinkedViewModel parentViewModel)
{
this.ChildControlList = new List<ilinked>();
ILinked ntc = Linked.Factory();
ntc.ViewModel = this;
ntc.ViewModelParent = parentViewModel;
ntc.ViewParent = parentView;
ntc.View = view;
this.LinkedValues = ntc;
if (parentViewModel != null)
{
if (parentViewModel.ChildControlList != null)
{
parentViewModel.ChildControlList.Add(ntc);
}
}
if (view is Window)
{
((Window)view).Closed += this.TracingViewModelClosed;
}
}
Observe that it also creates an event for the Closed
windows event. This event is used to remove the Linked instance from the parent list of children,
as you can see in the following code:
public void Dispose()
{
var copyList = new List<ilinked>();
copyList.AddRange(this.ChildControlList);
foreach (ILinked tc in copyList)
{
var view = tc.View;
if (view is Window)
{
((Window)view).Close();
}
}
if (this.LinkedValues != null && this.LinkedValues.ViewModelParent != null)
{
var vmp = this.LinkedValues.ViewModelParent;
vmp.ChildControlList.Remove(this.LinkedValues);
}
}
If a window is closed, automatically close all child windows that exist in
ChildControlList
and also removes from the parent
window, information about it from the parent ChildControlList
.
Then the control of the window is manipulated automatically by the base class and you don’t need to create code
to do this.
The presented LinkedViewModel
also implements INotifyPropertyChanges
to support the MVVM support for properties.
Using the code
To use these classes simply inherit your View Models from
LinkedViewModel
as shown in the following code.
public class UserRootViewModel : LinkedViewModel
{
...
}
To create your root view model class and windows, simply declare it in the App.axml.cs of your project and create with the following code:
public partial class App
{
private readonly Window main;
private readonly LinkedViewModel vm;
public App()
{
this.main = new UserRootView();
this.vm = new UserRootViewModel(this.main, null, null);
this.main.DataContext = this.vm;
this.main.Show();
}
}
In this case we set the parent view and view model to null because they do not exist.
To create a window child, simply declare the View Model of the new
window as the root class and create your view. Then create in the same form, but in this case fill the parent parameters. See the following example code:
private void CreateMultipleWindow()
{
var view = new WindowsChild();
var viewModel = new WindowsChildViewModel(view, this.LinkedValues.View, this);
view.DataContext = viewModel;
view.Show();
}
You can also use the control of instance of the LinkedViewModel
to see if more instances of the windows that you want to create
exist. This auxiliary procedure helps you to limit the number of windows that
needs to be created at the same time. To use this functionality you only need to call the static method
TestMultiplicyOverrun
. See the following example:
private void CreateSingleWindow()
{
var view = new WindowsChild();
if (LinkedViewModel.TestMultiplicyOverrun(view, this, 1))
{
view.Close();
return;
}
var viewModel = new WindowsChildViewModel(view, this.LinkedValues.View, this);
view.DataContext = viewModel;
view.Show();
}
The routine simply scans the list of children in the view model and then returns true if the number of active windows of the same type is greater than the number of windows allowed (the last parameter of the method).
See the code for a complete example. The example also supports MVVM to illustrate a complete application with the pattern.
Points of interest
The article offers a simple method to control child windows in an SDI application using WPF and MVVM.
History