This blog post looks at the problem of showing modal dialog windows in applications that target both the Silverlight and WPF platforms. A solution is provided which allows modal dialogs to be written that work well for both technologies.
Silverlight is, roughly speaking, a subset of Windows Presentation Foundation (WPF). This means that it is possible to write applications that target both frameworks, allowing you to develop applications that work both on the desktop and on the web using the same codebase.
However, Silverlight is not a strict subset of WPF, the differences between the two frameworks can be summed up as follows:
- Things that WPF has that Silverlight does not
- Things that Silverlight has that WPF has not
- Things that both frameworks have, but are different!
There are a great many things that the WPF framework has that Silverlight does not, mostly due to the size constraints of Silverlight being a browser plugin.
- There are a few things that Silverlight has, such as Out Of Browser (OOB) support and local storage which are not in WPF because they relate to the browser context.
- If these areas are avoided, the creation of a cross-platform application can be straightforward, however it is point (3) that causes problems.
When developing an application which targets both frameworks, it makes sense to do most of your development primarily in Silverlight in order to avoid accidentally using WPF features. In practical terms, the way most people achieve this is to create two projects, one Silverlight, one WPF. The WPF project will include links to the files within the Silverlight project. (This is achieved by selecting “Add existing item …”, then selecting the “Add as link” option.) The following diagram highlights the linked files in the attached project:
The first obstacle to overcome is the entry point into the application. Silverlight applications render a single UserControl
as the RootVisual
of the instantiated application, whereas WPF applications will create a Window
instance when launched. This problem is easily solved by simply hosting the UserControl
, which is the starting point for the Silverlight application, within a Window
:
<Window x:Class="ModalDialogDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModalDialogDemo"
Title="Modal dialog demo" SizeToContent="WidthAndHeight">
<Grid>
<local:MainControl/>
</Grid>
</Window>
With this small obstacle cleared, you can create some pretty decent cross-framework applications (as long as you avoid the temptation of looking at all the WPF controls you are missing out on!).
I have created quite a few WPF / Silverlight applications in this manner, and the process, although a little clunky (I usually have two Visual Studio instances open so that I can keep the two versions in synch), works pretty well. However, the next major obstacle I experienced is that of creating a modal dialog. This falls firmly into category (3).
Modal dialog functionality was added in Silverlight 3. To pop-up a modal dialog, create a page which inherits from ChildWindow
(rather than the usual UserControl
). This class provides a Show
method which pops up the dialog, and a Closed
event which is raised when the user hits the OK or Cancel. For example, you might create an error dialog and use it as follows:
ErrorDialog dlg = new ErrorDialog ();
dlg.Closed += new EventHandler(OnErrorDialogClosed);
dlg.Show();
Visual Studio has a template for the creating of modal dialogs via ChildWindow
:
However, with WPF, things are a bit different. Whilst Silverlight modal dialogs are constrained to live within the Silverlight container on your web page, WPF dialogs are proper windows that you can move around your desktop (Interestingly, someone on stackoverflow has emulated Silverlight’s ChildWindow in WPF, although I am not convinced that this will look right on a desktop application). Visual Studio does not have a template for creating modal dialogs in WPF, however, there is more than enough documentation regarding the various types of dialog on MSDN.
With WPF, a Window
can be shown as modal as follows:
ErrorDialog dlg = new ErrorDialog ();
dlg.ShowDialog();
There are a few differences here, firstly Window
has a Show
method just like Silverlight’s ChildWindow
, however, this will result in showing a modeless window. Instead we invoke the ShowDialog
method which shows a modal dialog, but there is another subtle difference, whilst ChildWindow.Show
returns immediately, with an event handler required to capture the modal dialog being closed, WPF’s Window.ShowDialog
blocks until the dialog is closed.
So … each framework has different classes for modal dialogs, and different interfaces for each. How do we resolve these differences?
Early in this blog post, we saw how the UserControl
which is the entry point into our Silverlight application can be hosted in a WPF Window
. The same approach can be used for modal dialogs. We can create our modal dialog as a UserControl
:
<UserControl x:Class="ModalDialogDemo.ModalDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="What is your favourite colour? " VerticalAlignment="Center"/>
<TextBox x:Name="colourInput" Grid.Column="1"
VerticalAlignment="Center" Width="80"/>
</Grid>
</UserControl>
With the above control hosted in a ChildWindow
or Window
depending on the platform, the problem is, we cannot explicitly make a reference to either of these types in our common code (i.e. the code shared by WPF / Silverlight via file linking). This problem can be solved by employing the Gang of Four Adapter pattern.
We define an interface that gives the functionality that we require for hosting our UserControl
as a modal dialog:
public interface IModalDialogHost
{
UserControl HostedContent { get; set; }
string Title { set; }
bool? DialogResult { get; }
void Show(DialogClosed closedCallback);
}
public delegate void DialogClosed(IModalDialogHost host);
Within the Silverlight project, we create a concrete implementation of this interface based on ChildWindow
:
<controls:ChildWindow x:Class="ModalDialogDemo.ModalDialogHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModalDialogDemo"
xmlns:controls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter x:Name="contentHost"/>
<Button x:Name="CancelButton" Content="Cancel"
Click="CancelButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click"
Width="75" Height="23"
HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</controls:ChildWindow>
With the interface implemented in code-behind as follows:
public partial class ModalDialogHost : ChildWindow, IModalDialogHost
{
...
public UserControl HostedContent
{
get
{
return (UserControl)contentHost.Content;
}
set
{
contentHost.Content = value;
}
}
public void Show(DialogClosed closedCallback)
{
Show();
Closed += (s, e) =>
{
closedCallback(this);
};
}
public new string Title
{
set { base.Title = value; }
}
}
Within WPF, we have a similar implementation based on Window
:
<Window x:Class="ModalDialogDemo.ModalDialogHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight"
ShowInTaskbar="False">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter x:Name="contentHost" Margin="10"/>
<Button x:Name="CancelButton" Content="Cancel"
Click="CancelButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75"
Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</Window>
Note that with WPF, we have a few extra configuration options to consider, such as the ShowInTaskbar
property. The code-behind again implements our adapter interface:
public partial class ModalDialogHost : Window, IModalDialogHost
{
...
public UserControl HostedContent
{
get
{
return (UserControl)contentHost.Content;
}
set
{
contentHost.Content = value;
}
}
public void Show(DialogClosed closedCallback)
{
ShowDialog();
closedCallback(this);
}
}
So, within each project, we have a framework specific implementation of this host. This allows us to create modal dialogs from the common / core code as follows:
IModalDialogHost dlg = new ModalDialogHost();
dlg.Title = "Pick a Colour";
dlg.HostedContent = new ModalDialog();
dlg.Show(DialogClosedHandler);
...
private void DialogClosedHandler(IModalDialogHost dialogHost)
{
string colour = ((ModalDialog)dialogHost.HostedContent).SelectedColour;
resultTextBox.Text = colour;
}
Here is a Silverlight application with a dialog implemented using this hosting concept:
[Unfortunately CodeProject does not support Silverlight content - you can see this application in action
on my blog.]
And here is a screenshot of the equivalent WPF application, where you can see the modal dialog is popped up as a window:
(If you want to have a go with the WPF app, you can download the source code from the link at the top of this blog.)
One final note … I have deliberately steered clear of the whole patterns business in this blog post. As you are probably aware, the Model-View-ViewModel (MVVM) pattern is very popular within Silverlight and WPF, and often other patterns such as Service Locator and Dependency Injection are thrown into the mix. However, the community has been struggling a little with how best to model modal dialog behaviours from within an MVVM ViewModel. Interestingly a couple of developers have supplied solutions on CodeProject and personal blogs, both faced questions and even criticisms that their approaches were not correct and unit testable (one of the central tenets of MVVM) and quickly followed up with articles demonstrating that you could in fact unit test their code (published here and here).
Whilst the use of a pattern such as MVVM which enforces a strong separation of application logic from the view will certainly help to solve problems such as WPF / Silverlight framework differences, in this particular instance, I wanted to show a simple technical solution rather than walk into the awaiting patterns minefield!
Regards, Colin E.