Modal dialogs in cross-platform WPF/Silverlight applications

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:

  1. Things that WPF has that Silverlight does not
  2. Things that Silverlight has that WPF has not
  3. Things that both framework 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 (1). 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 (2). 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, wheres 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 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:

/// <summary>
/// An interface which provides a host for some content which should be rendered
/// as a modal dialog
/// </summary>
public interface IModalDialogHost
{
    /// <summary>
    /// Gets or sets the content to host
    /// </summary>
    UserControl HostedContent { get; set; }

    /// <summary>
    /// Gets the dialog title
    /// </summary>
    string Title { set; }

    /// <summary>
    /// Gets the dialog result (i.e. OK, Cancel)
    /// </summary>
    bool? DialogResult { get; }

    /// <summary>
    /// Shows the dialog, invoking the given callback when it is closed
    /// </summary>
    void Show(DialogClosed closedCallback);        
}

/// <summary>
/// A callback which is invoked when a modal dialog is closed.
/// </summary>
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>

        <!-- the hosted content -->
        <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:

/// <summary>
/// A Silverlight implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : ChildWindow, IModalDialogHost
{
    ...

    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }

    public void Show(DialogClosed closedCallback)
    {
        Show();

        // handle the Closed event, invoking the callback provided by the client
        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>

        <!-- the hosted content -->
        <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:

/// <summary>
/// A WPF implementation of IModalDialogHost
/// </summary>
public partial class ModalDialogHost : Window, IModalDialogHost
{
    ...

    public UserControl HostedContent
    {
        get
        {
            return (UserControl)contentHost.Content;
        }
        set
        {
            contentHost.Content = value;
        }
    }

    public void Show(DialogClosed closedCallback)
    {
        ShowDialog();

        // invoke the callback when the dialog is closed
        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:

And here is a screenshot of the equivalent WPF application, where you can see the modal dialog is popped up as a window:

(You can download the sourcecode below if you want to have a go with the WPF app)

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!

You can download the full sourcecode for this article here: ModalDialogDemo.zip

Regards, Colin E.

blog comments powered by Disqus