In the previous post we covered how to show Views and how to wire them up with their ViewModels while preserving ability to correctly see their preview in Visual Studio and Blend design mode.

In this post i would like to tackle another problem that is very common in MVVM and yet very rarely done correctly.

I will try to provide simple and platform agnostic way of showing Modal Dialogs in MVVM manner without any code in View (later in the post we will reuse same concept and provide way to show simple message dialogs in the same way).

For those of you that have no patience here is the demo application that shows dialogs in action. On the main page there are two identical views each of them showing a list of users. For each user if you click the Edit button you will see the modal dialog to edit users properties.
Del button shows the message dialog to confirm deleting of the user n or to cancel it.

So lets begin: Actual implementation of the concept will be done in Silverlight 4 but it can be easily and without any pain converted to any other UI platform like WPF etc.

Our main design goals are:

  1. Platform agnostic solution to show modal dialogs that could be easily reused in WPF (or any other platform that supports concept of dialogs)
  2. We want our modal dialogs to be triggered by some UI event in our Views (like click on the button etc) but not from codebehind of the view
  3. Actual logic of showing dialogs must reside in ViewModel and NEVER (oh never) in code behind of the dialog (View)
  4. Dialogs must support MVVM pattern themselves so they should be able to bind to their ViewModel if needed
  5. Dialogs should be able to return result to the caller (maybe we wont need this always but there must be a way if needed)

Nice thing that could help us a lot is that Silverlight 3/4 comes with very handy ChildWindow control that is perfect fit for what we are trying to do, so we will try to reuse it in smart way.

The question remains how to use Silverlight ChildWindow class with MVVM pattern and to remain decoupled from anything that is platform specific about it.

We will first take the most important things that each Modal Dialog should have and extract this to separate interface IModalWindow:

  public interface IModalWindow
  {
    bool? DialogResult { get; set; }
    event EventHandler Closed;
    void Show();
    object DataContext { get; set; }
    void Close();
  }

As you can see IModalWindow is almost like an 1:1 abstraction of ChildWindow class :)
But its important to note here that IModalWindow contains only pure .NET constructs so it does not have anything Silverlight specific so that means we can implement this interface and create modal dialog controls in any .NET platform (WPF, WinForms etc).

IModalWindow has Show and Close methods, then it has DataContext so we can bind it to its ViewModel, then there is DialogResult boolean property to tell us if user confirmed the action or not, and it has event handler so we can hook to it when dialog is closed to pickup some results or do other actions (if we need it).

In order to be able to actually show dialogs that implement IModalWindow to our user we will introduce another layer of abstraction – it will be service that implements IModalDialogService interface that will have generic methods for showing the dialogs and completely hide the details of the implementation – as we said this should be platform agnostic so interface will contain nothing specific to Silverlight.

here is how the IModalDialogService interface looks:

  public interface IModalDialogService
  {
    void ShowDialog<TViewModel>(IModalWindow view, TViewModel viewModel, Action<TViewModel> onDialogClose);

    void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel);
  }

Our service interface defines two methods, first is one when when we want to specify OnClose handler so that we can do some action when dialog is closed and second overload is for showing modal dialogs in ‘Fire and forget’ mode without knowing when or how the dialog is closed.

Both ShowDialog methods are generic, accepting TViewModel type where we specify type of the ViewModel for our dialog (usually this is ViewModel interface). If we don’t need ViewModel for the dialog we can always set this to null.

So when showing dialog we pass the actual dialog – the View (i will explain later how we will get it) and its ViewModel and optionally we define an Action that is called when the dialog is closed (with the ViewModel instance of the dialog as the parameter – this is how we can get the results of the dialog or data user entered on the dialog etc).

And finally lets see how the Silverlight version of the ModalDialogService is implemented:

  public class ModalDialogService : IModalDialogService
  {
    public void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose)
    {
      view.DataContext = viewModel;
      if (onDialogClose != null)
      {
        view.Closed += (sender, e) => onDialogClose(viewModel);
      }
      view.Show();
    }

    public void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel)
    {
      this.ShowDialog(view, viewModel, null);
    }
  }

Implementation as you can see is very simple.
Service just sets the passed ViewModel to the DataContext of the passed dialog view, attaches the OnClose handler if we provided it and finally shows the view.

Now lets see the XAML and codebehind of one simple dialog we defined in sample application – dialog to edit user detials:

<controls:ChildWindow x:Class="MvvmModalDialogs.Views.EditUserModalDialogView"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" xmlns:DummyViewModels="clr-namespace:MvvmModalDialogs.ViewModels.DummyViewModels" Width="400" Height="300"
           Title="Edit User" d:DataContext="{Binding Source={StaticResource viewModel}}" >
    <controls:ChildWindow.Resources>
        <DummyViewModels:DummyEditUserModalDialogViewModel x:Key="viewModel" ></DummyViewModels:DummyEditUserModalDialogViewModel>
    </controls:ChildWindow.Resources>
  <Grid x:Name="LayoutRoot" Margin="2">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

        <TextBlock Text="{Binding Path=User.FullName}" HorizontalAlignment="Center" VerticalAlignment="Center" />

        <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center">
            <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,5,20,5"  />
            <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,5,20,5"  />
        </StackPanel>

        <Grid Height="142" HorizontalAlignment="Center" Margin="30,17,0,0" Name="grid1" VerticalAlignment="Center" Width="310">
            <Grid.RowDefinitions>
                <RowDefinition Height="32*" />
                <RowDefinition Height="33*" />
                <RowDefinition Height="32*" />

            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="84*" />
                <ColumnDefinition Width="31*" />
                <ColumnDefinition Width="195*" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0" Height="23" HorizontalAlignment="Right"  Text="User Id:" VerticalAlignment="Center"  />
            <TextBlock Height="23" HorizontalAlignment="Stretch"  Text="{Binding User.Id}" VerticalAlignment="Center" Grid.Row="0" Grid.Column="2" />

            <TextBlock Grid.Row="1" Grid.Column="0" Height="23" HorizontalAlignment="Right"  Text="Username:" VerticalAlignment="Center"  />
            <TextBox Height="23" HorizontalAlignment="Stretch"  Text="{Binding User.Username, Mode=TwoWay}" VerticalAlignment="Center" Grid.Row="1" Grid.Column="2" />

            <TextBlock Grid.Row="2" Grid.Column="0" Height="23" HorizontalAlignment="Right"  Text="Is Admin:" VerticalAlignment="Center"  />
            <CheckBox Height="23" HorizontalAlignment="Stretch" IsChecked="{Binding User.IsAdmin, Mode=TwoWay}" VerticalAlignment="Center" Grid.Row="2" Grid.Column="2" />

        </Grid>
    </Grid>
</controls:ChildWindow>

So its just simple ChildWindow control, that has few TextBoxes to show and edit details of one User instance.

And in the codebehind of the ChildWindow control we just mark it as implementor of our IChildWindow interface (and we set the DialogResult to true if user clicked OK button, but this can be done in ‘pure’ way via DelegateCommand i just used codebehind here for simplicity and because its not part of the business logic, its concern of the UI to tell us if the user closed dialog by clicking on OK button or not).

    public partial class EditUserModalDialogView : IModalWindow
    {
        public EditUserModalDialogView()
        {
            InitializeComponent();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }
    }
 

So all this gives us a completely platform agnostic solution to modal dialogs, and yet we used the powerful ChildWindow control that comes with Silverlight.
If we later decide to implement our modal dialogs in a different way we can do this easily just by creating a different control that implements IChildWindow and we are cool, nothing needs to be changed in our code that shows the dialogs.

So now how would we use this dialog in code?

There is one more peace of the puzzle to solve. We need to register our dialog controls somehow so we can easily pass them to our IModalService to show them so i will use ServiceLocator pattern for this.

We will create a Bootstrapper class that will be called in App.xaml.cs on application startup and this class will have task to register all common dialogs that we will be using in our application:

public class Bootstrapper
    {
        public static void InitializeIoc()
        {
            SimpleServiceLocator.SetServiceLocatorProvider(new UnityServiceLocator());
            SimpleServiceLocator.Instance.Register<IModalDialogService, ModalDialogService>();
            SimpleServiceLocator.Instance.Register<IMessageBoxService, MessageBoxService>();
            SimpleServiceLocator.Instance.Register<IMainPageViewModel, MainPageViewModel>();

            SimpleServiceLocator.Instance.Register<IModalWindow, EditUserModalDialogView>(Constants.EditUserModalDialog);
        }
    }

As you can see we are registering (among other things) EditUserModalDialogView that is in fact ChildWindow control as implementor of IModalWindow interface under a name from constants (Constants is the class holding distinct string names for each of the dialog types we want to use in application).

So now that everything is set, and our ServiceLocator knows of our dialogs, we can retrieve new instance of any of our dialogs to show them from the ViewModel of some view (when some button is clicked inside a DelegateCommand etc).

In this example i have only one dialog type that is implementing IModalWindow interface – EditUserModalDialogView, but i could have had many different types of dialogs that i will be showing to users so i would register them all in the Boostrapper as implementors of IModalWindow but under different named constants.
Then i would just retrieve the one i need by asking my service locator for a type that implements IModalWindow and supplying a string key that would determine which dialog exactly i need.

For example to retrieve instance of EditUserModalDialogView i would type this:

var dialog = SimpleServiceLocator.Instance.Get<IModalWindow>(Constants.EditUserModalDialog);

In order to just show dialog without any ViewModel or OnClose callback i would do this:

var dialog = SimpleServiceLocator.Instance.Get<IModalWindow>(Constants.EditUserModalDialog);
this.modalDialogService.ShowDialog(dialog, null);

or to set ViewModel for the dialog i could do this:

var dialog = SimpleServiceLocator.Instance.Get<IModalWindow>(Constants.EditUserModalDialog);
this.modalDialogService.ShowDialog(dialog, new EditUserModalDialogViewModel
{
User = userInstanceToEdit
});

These were all simple cases. Now lets see how we can show dialog from DelegateCommand in ViewModal when user clicks on some Button in View, and also let’s set a callback function to be called when dialog is closed so we can do something meaningful like updating user details:

First we crate button on View to trigger the action and bind its Command property to a DelegateCommand in ViewModel and use binding for CommandParameter (in this case binding is the actual User instance):

<Button Content="Edit" Command="{Binding Source={StaticResource viewModelLocator}, Path=ViewModel.ShowUserCommand}" CommandParameter="{Binding}" />

And now in the ViewModel in the ShowUserCommand DelegateCommand we place the code to show the dialog and update Users collection afterwards if needed:

        this.ShowUserCommand = new DelegateCommand<User>(userInstanceToEdit =>
        {
          var dialog = SimpleServiceLocator.Instance.Get<IModalWindow>(Constants.EditUserModalDialog);
          this.modalDialogService.ShowDialog(dialog, new EditUserModalDialogViewModel
          {
            User = userInstanceToEdit
          },
          returnedViewModelInstance =>
          {
            if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
            {
            var oldPos = this.Users.IndexOf(userInstanceToEdit);
            this.Users.RemoveAt(oldPos);
            this.Users.Insert(oldPos, returnedViewModelInstance.User);
            }
            });
        });

So basically im setting the DelegateCommand (see details of the DelegateCommand implementation in the project attached to the post) with code to retrieve the Edit User dialog from the ServiceLocator.
Then im calling services ShowDialog method, passing the new instance of EditUserModalDialogViewModel as the ViewModel of the dialog (containing the user instance i want to edit) and im passing lamda for onDialogClose callback to remove old instance of the User from Users collection on ViewModel and replace it with the new one we received from the dialog.
Off course im first checking if user clicked on Ok button (using dialog.DialogResult property to check that).

And there you have it: simple MVVM solution for modal dialogs that you can port to any platform and implement with any kind of control or popup you want.

To show this concept in more detail i created another similar service to show message boxes to the user.
Again we have the service interface – IMessageBoxService that offers two ways of showing message boxes, first one more complex where we specify message, caption and buttons displayed on the message box and second one, simpler where we just specify message and caption, so only OK button is shown to the user:

  public interface IMessageBoxService
  {
    GenericMessageBoxResult Show(string message, string caption, GenericMessageBoxButton buttons);
    void Show(string message, string caption);
  }

I also created abstractions for the common button types and return results so we don’t depend on Silverlight MessageBox class (that we will use underneath) is using:

  public enum GenericMessageBoxButton
  {
    Ok,
    OkCancel
  }

  public enum GenericMessageBoxResult
  {
    Ok,
    Cancel
  }

So lets see how the actual Silverlight implementation of IMessageBoxService interface would look like:

  public class MessageBoxService : IMessageBoxService
  {
    public GenericMessageBoxResult Show(string message, string caption, GenericMessageBoxButton buttons)
    {
      var slButtons = buttons == GenericMessageBoxButton.Ok
                        ? MessageBoxButton.OK
                        : MessageBoxButton.OKCancel;

      var result = MessageBox.Show(message, caption, slButtons);

      return result == MessageBoxResult.OK ? GenericMessageBoxResult.Ok : GenericMessageBoxResult.Cancel;
    }

      public void Show(string message, string caption)
      {
          MessageBox.Show(message, caption, MessageBoxButton.OK);
      }
  }

Again its very simple, we are using the built in Silverlight’s MessageBox class to show the actual message box but later we can decide its not good enough and implement it in another way (maybe with 3rd party message box component etc) yet our code in ViewModel wont change since it will be talking only to our interface.

Here is the example of how i use this MessageBoxService in the demo project for this post:

        this.DeleteUserCommand = new DelegateCommand<User>(p =>
        {
          var result = this.messageBoxService.Show(string.Format("Are you sure you want to delete user {0} ???", p.Username),
              "Please Confirm", GenericMessageBoxButton.OkCancel);
          if (result == GenericMessageBoxResult.Ok)
          {
            this.Users.Remove(p);
          }
        });

What i do here is just set the DelegateCommand in ViewModel to first show the message box with Ok and Cancel buttons and if user pressed Ok do some action – delete from the list.

So i think this two solutions covered all design goals we set on the start: we have decoupled way of showing modal dialogs and message boxes currently done in Silverlight but they could be easily converted to another platform.
Also approach is MVVM friendly without any hacks so any pattern purist can be satisfied with it.
If you have some concerns or suggestions on this approach i would be happy to hear it!

Here is the attached VS 2010 solution with full source code from the post.
Live demo of the application showing modal dialogs and message boxes flying around the screen.