Introduction
Hello! I'm a WPF/Prism developer. I found a need to open real windows in my WPF/Prism applications. The Prism provides a good way to open pop-up windows but not real ones. If the application follows MVVM pattern, this is bad practice to open a new window from view model code . Sometimes, I would like to open and handle the window from XAML. How can it be performed?
Background
The key for the solution are behaviors. The basic idea is to write (blend) behavior that will open windows upon a request and manage them. So windows will be defined in XAML. We will need some proxy object which allows us to access our behavior if we want to show a dialog from code or handle the window by a trigger:
Additionally, we need to define routed commands which allows us to perform windows operation from XAML:
Using the Code
Let me to present WOP library.
First of all, it contains window open behavior which allows to define windows in XAML. This code is located in main window:
<i:Interaction.Behaviors>
<wop:OpenWindowsBehavior>
<wop:WindowDefinition Type="{x:Type samples:Dialog1}"
Key="Dialog1"
DataContext="{StaticResource DialogViewModel}"/>
</wop:OpenWindowsBehavior>
</i:Interaction.Behaviors>
For the behavior following additional properties can be given:
WindowsOperationManager
- This is our proxy object to communicate with triggers or view model. Normally, there is no need to specify the manager, WindowsOperationManager.Default
manager will be used.
DefaultType
- Sets default window type. If no type is specified in a window definition, this type will be used.
For each window, a WindowDefinition
row should be added. Here we need to specify unique window key which will be used to identify the window. The DataContext
can be specified or given in a view model code when window is open. Instead of giving the data context object, we can also specify DataContextType
, then a new data context object will be instantiated automatically. Additionally, the following parameters can be provided:
Title
- Sets the created window title. When set, overrides original window title.
WindowStartupLocation
- Sets created window start up position. Default value is CenterOwner
.
Top
- Sets created window Top. This property is ignored if WindowStartupLocation
isn't Manual.
Left
- Sets created window Left. This property is ignored if WindowStartupLocation
isn't Manual.
MultipleInstances
- Sets the flag which determines this window definition can be used to open multiple window instances. In this case, window should not be used as modal dialog.
PropagateCommands
- Sets the flag which determines if DialogCommands
should be propagated to the created window. Default value is true
.
Let's say we defined our window how now can they be opened?
First of all, we can open the window from the main window view model code in the following way:
private void OnOpenWindowCommand(object prm)
{
DialogViewModel vm = new DialogViewModel();
bool? result = WindowsOperationManager.Default.ShowDialog("Dialog1", vm);
}
Accordingly to Show
and ShowDialog
methods of the Window
class, the execution will proceed or be blocked until the window is closed. The dialog result will be returned. Notice that we need to know only the window key but not type. Mocking the WindowsOperationManager
provides us the view model unit testing capabilities.
For more details, please examine SimpleDialog1
sample.
The manager implements IWindowOperationManager
interface which provides a rich API to open, close and handle windows defined by the behavior.
SimpleDialog2
demonstrates how windows can be opened and handled from XAML. DialogCommands
are handed by the behavior (actually command bindings are set to the associated object and propagated to each new window). In order to open a window, we can execute DialogCommand.Show
or DialogCommand.ShowDialog
command:
<!---->
<Button Content="Show dialog"
Command ="wop:DialogCommands.ShowDialog"
CommandParameter="Dialog1"/>
In order to close the window, we can use DialogCommand.Close
command. DialogCommand.CloseTrue
and DialogCommand.CloseFalse
will set the dialog result as well:
<!---->
<Button Content="Ok" Command="wop:DialogCommands.CloseTrue"/>
<Button Content="Cancel"
Command="wop:DialogCommands.CloseFalse" IsDefault="True"/>
How can the dialog result or a dialog data be handled in XAML?
We can use interactivity triggers to accomplish this. The wop library provides WindowClosedTrigger
and S
etFromWindowDataContextAction
classes. WindowClosedTrigger
is activated when window is closed. Desired window key and dialog result can be specified. Notice that internally the trigger uses WindowsOperationManager
to access window objects. If required, the default manager can be replaced with custom one by setting WindowsOperationManager
property. SetFromWindowDataContextAction
provides ability to take a data from the window data context object and set it to the specified target's property:
<i:Interaction.Triggers>
<wop:WindowClosedTrigger DialogResult="True" Key="Dialog1">
<wop:WindowClosedTrigger.Actions>
-->
-->
-->
<wop:SetFromWindowDataContextAction TargetName="txtDialogData"
PropertyName="Text"
WindowDataContextProperty="DialogData"/>
</wop:WindowClosedTrigger.Actions>
</wop:WindowClosedTrigger>
</i:Interaction.Triggers>
SetFromWindowDataContextAction
allows to specify TargetObject
instead of TargetName
. This can be useful when you need to take a data from dialog view model and set it to the main view model.
Consider SimpleDialog2
for more details.
The WOP library provides the ability to open and handle multiple instances of the same window. In this case, the window gets the same key but different index. MultiInstance
sample demonstrates this.
NestedDialogs
sample shows how one dialog can be opened from another.
Sometimes, we need to share a data given by one window to others in time when the window is still open.
WOP provides the following mechanism to implement this:
When a window wants to share part of its view model data, it can execute DialogCommand.Post
command. This command will be handled by WindowPostTrigger
provided by the library. The trigger is the same as WindowClosedTrigger
and has the same properties. SetFromWindowDataContextAction.
TargetName
can be set to currently open window key. In this way, a data of post window view model can be set to other (currently open) window data context:
<i:Interaction.Triggers>
<wop:WindowPostTrigger Key="{x:Static samples:WindowsList.WINDOW1}">
-->
-->
-->
<wop:SetFromWindowDataContextAction
TargetName="{x:Static samples:WindowsList.WINDOW2}"
PropertyName="Value" WindowDataContextProperty="Value"/>
<wop:SetFromWindowDataContextAction
TargetName="{x:Static samples:WindowsList.WINDOW3}"
PropertyName="Value" WindowDataContextProperty="Value"/>
</wop:WindowPostTrigger>
</i:Interaction.Triggers>
For additional information, review MessagePosting sample.
WOP library is well XML- documented and blendable. Have a good time playing with it.
All code is written in C#, framework 4.5 VS2012.
Points of Interest
Interaction triggers and blend behaviors provide a wide range of extension for WPF and abilities to solve technical problems which pops up when we try to implement MVVM using WPF. Additionally, they provide solid code block which can be reusable in many situations.
History
- 28th June, 2013: Initial version