Introduction
Wouldn't it be great if you could use dialogs like any other control in XAML? That is, drop in a <Dialog>
element and have a modal dialog window appear? This would let Model-View-ViewModel (MVVM) programs show dialogs without touching the XAML's code behind; a feat not currently possible with Windows Presentation Foundation (WPF). This article presents the Dialog control which both simplifies dialogs and makes them work directly with MVVM.
Background
True proponents of MVVM believe that MVVM applications should have no substantial code behind for XAML files. This restriction guarantees that no ViewModel logic accidentally creeps into the View. Unfortunately, out of the box it's impossible to implement a dialog without writing some code behind. And even with code behind, the dialog won't have access to the automatic data template features of WPF because the dialog is treated as a separate logical tree. Because of these limitations, most MVVM applications simply don't use dialogs. This control solves many of these problems.
Using the Code
The dialog control is defined in the "DialogControl" folder of the sample code. It's all under the namespace MVVMDialogSample.DialogControl
. To use it in an XAML file, first add the namespace to the parent element of your XAML:
xmlns:dialog="clr-namespace:MVVMDialogSample.DialogControl"
Then add the Dialog
as a child control. The dialog control won't actually appear in the UI, so simply place it where it will correctly inherit any required data templates. A simple dialog element looks like this:
<dialog:Dialog Content="Hello World!" Showing="True" />
After adding the control to your XAML, a window may appear. This is because the Visual Studio XAML previewer is "previewing" your dialog. You have to close it before you can use Visual Studio again as it is a modal dialog. You can hide the dialog by setting Showing="False"
. Alternatively, the Showing
property defaults to false
.
Run the code and a window will appear with the content "Hello World!
".
Much more interesting is to set or bind the Content
property to a complex object. The dialog will pick up any applicable data templates from the main window and apply them to the content shown in the dialog window.
In MVVM, the most common usage of Dialog
is to bind a property of the ViewModel to the Content
property of the Dialog
. For example:
<dialog:Dialog Content="{Binding Path=DialogViewModel}" />
Then, set a trigger on the Dialog
which shows the dialog only when it has Content
. To show the dialog window, simply set the bound ViewModel property to whatever you want to display in the dialog. To hide it, simply set the bound property to null
. Here's a style that shows the dialog only when it has content:
<Style TargetType="{x:Type dialog:Dialog}">
<Style.Triggers>
<Trigger Property="HasContent" Value="True">
<Setter Property="Showing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
Additionally, most MVVM applications will bind a ViewModel
ICommand
to the CancelCommand
property. The Dialog
will invoke the command stored in the CancelCommand
property if the user closes the dialog by clicking the X. This allows the ViewModel to clean up if the user forcibly cancels.
Because the dialog control inherits from ContentControl
, it has some extra properties that don't have any effect. In fact, the only properties that do anything are as follows:
Content
- See the documentation for ContentControl
.
ContentStringFormat
- See the documentation for ContentControl
.
ContentTemplate
- See the documentation for ContentControl
. Unfortunately, WPF has a bug where setting this property on a Window with a Template that contains a ContentPresenter
will throw an exception, rendering this property pretty much useless. Thus, to format content you must rely on ContentTemplateSelector
or a DataTemplate
.
ContentTemplateSelector
- See the documentation for ContentControl
. If this property and ContentTemplate
property are unspecified, the dialog control will attempt to find an appropriate data template.
DataContext
- See the documentation for ContentControl
.
Title
- The title of the dialog.
Showing
- Whether the dialog is showing or not.
CancelCommand
- ICommand
that is invoked when the dialog is cancelled.
WindowTemplate
- The ControlTemplate
for the Window. Must have a root element that derives from or is of type Window
. A default Window
is specified in "Themes\Generic.xaml".
Points of Interest
This control works mainly by binding properties of Dialog
control to an instance of Window
so the Window
stays in sync. However, getting the DataTemplate
from the dialog control to the dialog window takes a little more work. The dialog window has a special DataTemplateSelector
which asks the dialog control what DataTemplate
to use. Since the dialog control is in the logical tree of the main window, it chooses the correct DataTemplate
. In this way, the dialog window gets the DataTemplate
as though it were a control in the main window's logical tree. This would all be made much easier if the Window
class could be added as a logical child of another tree but the code specifically disallows this.
The control also delays evaluation of the dialog's visibility when the Showing
property changes. This is to prevent the dialog's ShowDialog()
and Hide()
from being called over and over again if the Showing
property is changed multiple times due to coercion or styles. It delays evaluation by hooking a delegate to the Windows message pump via Application.Current.Dispatcher
. The delegate evaluates the visibility once. While this is not strictly necessary, it reduces the number of Hide()
and ShowDialog()
calls that are made in complicated cases.
History
- 2009/04/25
- Made control lookless
Window
is destroyed and recreated on each ShowDialog()
and Close()
which makes IsCancel
work all the time
- Moved
Window
specification to "Generic.xaml" and added WindowTemplate
property
- Hooked up
ContentStringFormat
, ContentTemplate
, ContentTemplateSelector
to Window
- 2009/04/15