Wpf – How to build a generic/re-usable modal dialog for WPF following MVVM

datatemplatemodal-dialogmvvmwpf

I would like to build a generic/re-usable modal dialog that I can use in our WPF (MVVM) – WCF LOB application.

I have a Views and associated ViewModels that I would like to display using dialogs. Bindings between Views and ViewModels are done using Type-targeted DataTemplates.

Here are some requirements that I have been able to draft:

  • I prefer this to be based on a Window instead of using Adorners and controls that act like a modal dialog.
  • It should get its minimum size from the content.
  • It should center on the owner window.
  • The window must not show the Minimize and Maximize buttons.
  • It should get its title from the content.

What is the best way to do this?

Best Answer

I usually deal with this by injecting this interface into the appropriate ViewModels:

public interface IWindow
{
    void Close();

    IWindow CreateChild(object viewModel);

    void Show();

    bool? ShowDialog();
}

This allows the ViewModels to spaw child windows and show them modally on modeless.

A reusable implementation of IWindow is this:

public class WindowAdapter : IWindow
{
    private readonly Window wpfWindow;

    public WindowAdapter(Window wpfWindow)
    {
        if (wpfWindow == null)
        {
            throw new ArgumentNullException("window");
        }

        this.wpfWindow = wpfWindow;
    }

    #region IWindow Members

    public virtual void Close()
    {
        this.wpfWindow.Close();
    }

    public virtual IWindow CreateChild(object viewModel)
    {
        var cw = new ContentWindow();
        cw.Owner = this.wpfWindow;
        cw.DataContext = viewModel;
        WindowAdapter.ConfigureBehavior(cw);

        return new WindowAdapter(cw);
    }

    public virtual void Show()
    {
        this.wpfWindow.Show();
    }

    public virtual bool? ShowDialog()
    {
        return this.wpfWindow.ShowDialog();
    }

    #endregion

    protected Window WpfWindow
    {
        get { return this.wpfWindow; }
    }

    private static void ConfigureBehavior(ContentWindow cw)
    {
        cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
        cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
    }
}

You can use this Window as a reusable host window. There's no code-behind:

<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.ContentWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:Ploeh.Samples.ProductManagement.WpfClient"
        xmlns:pm="clr-namespace:Ploeh.Samples.ProductManagement.PresentationLogic.Wpf;assembly=Ploeh.Samples.ProductManagement.PresentationLogic.Wpf"
        Title="{Binding Path=Title}"
        Height="300"
        Width="300"
        MinHeight="300"
        MinWidth="300" >
    <Window.Resources>
        <DataTemplate DataType="{x:Type pm:ProductEditorViewModel}">
            <self:ProductEditorControl />
        </DataTemplate>
    </Window.Resources>
    <ContentControl Content="{Binding}" />
</Window>

You can read more about this (as well as download the full code sample) in my book.