Click here to Skip to main content
11,645,087 members (67,607 online)
Click here to Skip to main content

Showing Dialogs When Using the MVVM Pattern

, 25 Jun 2015 Apache 242.2K 9.2K 195
Rate this:
Please Sign up or sign in to vote.
A framework to solve the problem of opening dialogs from a view model when using the MVVM pattern in WPF.

Contents

Introduction

This article will address one of the problems you might run into when using the MVVM pattern, namely opening dialogs from view models. Basic knowledge of the pattern is expected. Josh Smith has written a fantastic article in MSDN Magazine which can serve as the starting point for those that are unfamiliar with the pattern.

There already exists numerous MVVM frameworks, and for those looking for a more complete solution to MVVM I would recommend taking a look at the following frameworks:

This framework is not a complete all-inclusive MVVM framework. It is designed to simplify the concept of opening dialogs from a view model when using MVVM in WPF. It does a pretty good job of that but nothing else. It doesn't contain any fancy view model base classes, nor any event broker or service locator. The only extra benefit you'll get is the ability to easily write unit tests for your view models in the same manner unit tests are written for other classes. That you will get.

The framework has built in support for opening the following dialogs:

  • Modal dialogs
  • Non-modal dialogs
  • Message boxes
  • Open file dialogs
  • Save file dialogs
  • Folder browser dialogs

Usage

More interesting than the implementation of the framework is the usage of it, so lets start with that. This chapter will demonstrate the code required to show the supported dialogs.

Showing a Dialog

A dialog can be shown either as modal or non-modal. A modal dialog halts code execution and awaits the dialog result while a non-modal dialog continues code execution without waiting for any dialog result. Showing a dialog can be performed in either of two ways, either by explicit specifying the dialog type or by implicit using the dialog type locator. Both concepts and the difference in usage is described in the upcoming chapters.

Explicit Dialog Type Syntax

The most straight forward syntax to use is the explicit syntax where the generic methods IDialogService.ShowDialog<T> and IDialogService.Show<T> shows a modal respectively non-modal dialog of the type T. The MVVM purists among the readers are most certainly appalled by the fact that the view type is defined in the view model. For them there is the implicit syntax and the dialog type locator.

Implicit Dialog Type Syntax and the Dialog Type Locator

Specifying a dialog type in a view model might either be unwanted or impossible in certain situations, thus the framework supports opening a dialog without specifying the dialog type. IDialogService.ShowDialog and IDialogService.Show are the non-generic methods where the dialog type isn't specified in the method call. However, IDialogService still has to know the dialog type in order to create and open the dialog. This is where the concept of a dialog type locator comes into play.

A dialog type locator is a function of type Func<INotifyPropertyChanged, Type> capable of resolving a dialog type based on a specified view model. The implementation of DialogService comes with a default dialog type locator that uses a common naming convention used in a multitude of articles and code samples regarding the MVVM pattern. The convention states that if the name of the view model is MyNamespace.ViewModels.MyDialogViewModel then the name of the dialog is MyNamespace.Views.MyDialog. If this convention doesn't fit your code structure the default locator can be overridden by specifying your own implementation in the constructor of DialogService.

Showing a Modal Dialog Using Explicit Dialog Type Syntax

To show a modal dialog using explicit dialog type syntax start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.Dialog.Modal.Views.ModalDialogTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

In the view model, open the dialog by calling IDialogService.ShowDialog<T>.

public class ModalDialogTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public ModalDialogTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void ShowDialog()
  {
    var dialogViewModel = new AddTextDialogViewModel();

    bool? success = dialogService.ShowDialog<AddTextDialog>(this, dialogViewModel));
    if (success == true)
    {
      Texts.Add(dialogViewModel.Text);
    }
  }
}

Showing a Modal Dialog Using Implicit Dialog Type Syntax

To show a modal dialog using implicit dialog type syntax start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.Dialog.Modal.Views.ModalDialogTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

Make sure the dialog type locator can locate the dialog type, and then let the view model open the dialog by calling IDialogService.ShowDialog.

public class ModalDialogTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public ModalDialogTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void ShowDialog()
  {
    var dialogViewModel = new AddTextDialogViewModel();

    bool? success = dialogService.ShowDialog(this, dialogViewModel));
    if (success == true)
    {
      Texts.Add(dialogViewModel.Text);
    }
  }
}

Showing a Non-Modal Dialog Using Explicit Dialog Type Syntax

To show a non-modal dialog using explicit dialog type syntax start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.Dialog.NonModal.Views.NonModalDialogTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

In the view model, open the dialog by calling IDialogService.Show<T>.

public class NonModalDialogTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public NonModalDialogTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void Show()
  {
    var dialogViewModel = new CurrentTimeDialogViewModel();
    dialogService.Show<CurrentTimeDialog>(this, dialogViewModel));
  }
}

Showing a Non-Modal Dialog Using Implicit Dialog Type Syntax

To show a non-modal dialog using implicit dialog type syntax start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.Dialog.NonModal.Views.NonModalDialogTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

Make sure the dialog type locator can locate the dialog type, and then let the view model open the dialog by calling IDialogService.Show.

public class NonModalDialogTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public NonModalDialogTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void Show()
  {
    var dialogViewModel = new CurrentTimeDialogViewModel();
    dialogService.Show(this, dialogViewModel));
  }
}

Showing a Message Box

To show a message box start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.MessageBox.Views.MessageBoxTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">

</UserControl>

In the view model, open the dialog by calling IDialogService.ShowMessageBox.

public class MessageBoxTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public MessageBoxTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void ShowMessageBox()
  {
    dialogService.ShowMessageBox(
      this,
      "This is the text.",
      "This Is The Caption",
      MessageBoxButton.OKCancel,
      MessageBoxImage.Information);
  }
}

Showing a Open File Dialog

To show a open file dialog start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.OpenFileDialog.Views.OpenFileTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

In the view model, open the dialog by calling IDialogService.ShowOpenFileDialog.

public class OpenFileTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public OpenFileTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void OpenFile()
  {
    var settings = new OpenFileDialogSettings
    {
      Title = "This Is The Title",
      InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
      Filter = "Text Documents (*.txt)|*.txt|All Files (*.*)|*.*"
    };

    bool? success = dialogService.ShowOpenFileDialog(this, settings);
    if (success == true)
    {
      Path = settings.FileName;
    }
  }

Showing a Save File Dialog

To show a save file dialog start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.SaveFileDialog.Views.SaveFileTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
    
</UserControl>

In the view model, open the dialog by calling IDialogService.ShowSaveFileDialog.

public class SaveFileTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public SaveFileTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void SaveFile()
  {
    var settings = new SaveFileDialogSettings
    {
      Title = "This Is The Title",
      InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
      Filter = "Text Documents (*.txt)|*.txt|All Files (*.*)|*.*",
      CheckFileExists = false
    };

    bool? success = dialogService.ShowSaveFileDialog(this, settings);
    if (success == true)
    {
      Path = settings.FileName;
    }
  }
}

Showing a Folder Browser Dialog

To show a folder browser dialog start by registering the view by decorating the XAML with the attached property DialogServiceViews.IsRegistered.

<UserControl
  x:Class="DemoApplication.Features.FolderBrowserDialog.Views.FolderBrowserTabContent"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:md="https://github.com/fantasticfiasco/mvvm-dialogs"
  md:DialogServiceViews.IsRegistered="True">
  
</UserControl>

In the view model, open the dialog by calling IDialogService.ShowFolderBrowserDialog.

public class FolderBrowserTabContentViewModel : INotifyPropertyChanged
{
  private readonly IDialogService dialogService;
  
  public FolderBrowserTabContentViewModel(IDialogService dialogService)
  {
    this.dialogService = dialogService;
  }
  
  private void BrowseFolder()
  {
    var settings = new FolderBrowserDialogSettings
    {
      Description = "This is a description"
    };

    bool? success = dialogService.ShowFolderBrowserDialog(this, settings);
    if (success == true)
    {
      Path = settings.SelectedPath;
    }
  }
}

The Dialog Service

IDialogService and the implementation DialogService are of most importance since they are the workhorses of the framework. Lets start by taking a look at IDialogService.

/// <summary>
/// Interface abstracting the interaction between view models and views when it comes to
/// opening dialogs using the MVVM pattern in WPF.
/// </summary>
public interface IDialogService
{
  /// <summary>
  /// Displays a non-modal dialog of specified type <typeparamref name="T"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <typeparam name="T">The type of the dialog to show.</typeparam>
  void Show<T>(
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel)
    where T : Window;

  /// <summary>
  /// Displays a non-modal dialog of a type that is determined by the dialog type locator.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  void Show(
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel);

  /// <summary>
  /// Displays a modal dialog of specified type <typeparamref name="T"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <typeparam name="T">The type of the dialog to show.</typeparam>
  /// <returns>
  /// A nullable value of type <see cref="bool"/> that signifies how a window was closed by
  /// the user.
  /// </returns>
  bool? ShowDialog<T>(
    INotifyPropertyChanged ownerViewModel,
    IModalDialogViewModel viewModel)
    where T : Window;

  /// <summary>
  /// Displays a modal dialog of a type that is determined by the dialog type locator.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <returns>
  /// A nullable value of type <see cref="bool"/> that signifies how a window was closed by
  /// the user.
  /// </returns>
  bool? ShowDialog(
    INotifyPropertyChanged ownerViewModel,
    IModalDialogViewModel viewModel);

  /// <summary>
  /// Displays a message box that has a message, title bar caption, button, and icon; and
  /// that accepts a default message box result and returns a result.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="messageBoxText">
  /// A <see cref="string"/> that specifies the text to display.
  /// </param>
  /// <param name="caption">
  /// A <see cref="string"/> that specifies the title bar caption to display. Default value
  /// is an empty string.
  /// </param>
  /// <param name="button">
  /// A <see cref="MessageBoxButton"/> value that specifies which button or buttons to
  /// display. Default value is <see cref="MessageBoxButton.OK"/>.
  /// </param>
  /// <param name="icon">
  /// A <see cref="MessageBoxImage"/> value that specifies the icon to display. Default value
  /// is <see cref="MessageBoxImage.None"/>.
  /// </param>
  /// <param name="defaultResult">
  /// A <see cref="MessageBoxResult"/> value that specifies the default result of the
  /// message box. Default value is <see cref="MessageBoxResult.None"/>.
  /// </param>
  /// <returns>
  /// A <see cref="MessageBoxResult"/> value that specifies which message box button is
  /// clicked by the user.
  /// </returns>
  MessageBoxResult ShowMessageBox(
    INotifyPropertyChanged ownerViewModel,
    string messageBoxText,
    string caption = "",
    MessageBoxButton button = MessageBoxButton.OK,
    MessageBoxImage icon = MessageBoxImage.None,
    MessageBoxResult defaultResult = MessageBoxResult.None);

  /// <summary>
  /// Displays the <see cref="OpenFileDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the open file dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  bool? ShowOpenFileDialog(
    INotifyPropertyChanged ownerViewModel,
    OpenFileDialogSettings settings);

  /// <summary>
  /// Displays the <see cref="SaveFileDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the save file dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  bool? ShowSaveFileDialog(
    INotifyPropertyChanged ownerViewModel,
    SaveFileDialogSettings settings);

  /// <summary>
  /// Displays the <see cref="FolderBrowserDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the folder browser dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  bool? ShowFolderBrowserDialog(
    INotifyPropertyChanged ownerViewModel,
    FolderBrowserDialogSettings settings);
}

The actual implementation of IDialogService is DialogService.

/// <summary>
/// Class abstracting the interaction between view models and views when it comes to
/// opening dialogs using the MVVM pattern in WPF.
/// </summary>
public class DialogService : IDialogService
{
  private static readonly Func<Type, Window> DefaultDialogFactory = (dialogType => (Window)Activator.CreateInstance(dialogType));
  private static readonly string DialogResultPropertyName = Members.GetPropertyName((IModalDialogViewModel viewModel) => viewModel.DialogResult);

  private readonly Func<Type, Window> dialogFactory; 
  private readonly Func<INotifyPropertyChanged, Type> dialogTypeLocator;

  /// <summary>
  /// Initializes a new instance of the <see cref="DialogService"/> class.
  /// </summary>
  /// <param name="dialogFactory">
  /// Function creating a dialog with a specified type. If no dialog factory is specified
  /// <see cref="Activator.CreateInstance(Type)"/> is used to create the dialog.
  /// </param>
  /// <param name="dialogTypeLocator">
  /// Function returning the dialog type based on view model instance. If no dialog type
  /// locator is specified a naming convention based approach is used where the dialog type
  /// is based on the name of the view model. The convention states that if the name of the
  /// view model is 'MyNamespace.ViewModels.MyDialogViewModel' then the name of the dialog is
  /// 'MyNamespace.Views.MyDialog'.
  /// 
  /// This naming convention is used in a multitude of articles and code samples regarding
  /// the MVVM pattern and is a good default strategy.
  /// </param>
  public DialogService(
    Func<Type, Window> dialogFactory = null,
    Func<INotifyPropertyChanged, Type> dialogTypeLocator = null)
  {
    this.dialogFactory = dialogFactory ?? DefaultDialogFactory;
    this.dialogTypeLocator = dialogTypeLocator ?? NamingConventionDialogTypeLocator.LocateDialogType;
  }

  #region IDialogService Members

  /// <summary>
  /// Displays a non-modal dialog of specified type <typeparamref name="T"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <typeparam name="T">The type of the dialog to show.</typeparam>
  public void Show<T>(
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel)
    where T : Window
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (viewModel == null)
      throw new ArgumentNullException("viewModel");

    Show(ownerViewModel, viewModel, typeof(T));
  }

  /// <summary>
  /// Displays a non-modal dialog of a type that is determined by the dialog type locator.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  public void Show(
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (viewModel == null)
      throw new ArgumentNullException("viewModel");

    Type dialogType = dialogTypeLocator(viewModel);
    Show(ownerViewModel, viewModel, dialogType);
  }

  /// <summary>
  /// Displays a modal dialog of specified type <typeparamref name="T"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <typeparam name="T">The type of the dialog to show.</typeparam>
  /// <returns>
  /// A nullable value of type <see cref="bool"/> that signifies how a window was closed by
  /// the user.
  /// </returns>
  public bool? ShowDialog<T>(
    INotifyPropertyChanged ownerViewModel,
    IModalDialogViewModel viewModel)
    where T : Window
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (viewModel == null)
      throw new ArgumentNullException("viewModel");

    return ShowDialog(ownerViewModel, viewModel, typeof(T));
  }

  /// <summary>
  /// Displays a modal dialog of a type that is determined by the dialog type locator.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="viewModel">The view model of the new dialog.</param>
  /// <returns>
  /// A nullable value of type <see cref="bool"/> that signifies how a window was closed by
  /// the user.
  /// </returns>
  public bool? ShowDialog(
    INotifyPropertyChanged ownerViewModel,
    IModalDialogViewModel viewModel)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (viewModel == null)
      throw new ArgumentNullException("viewModel");

    Type dialogType = dialogTypeLocator(viewModel);
    return ShowDialog(ownerViewModel, viewModel, dialogType);
  }

  /// <summary>
  /// Displays a message box that has a message, title bar caption, button, and icon; and
  /// that accepts a default message box result and returns a result.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="messageBoxText">
  /// A <see cref="string"/> that specifies the text to display.
  /// </param>
  /// <param name="caption">
  /// A <see cref="string"/> that specifies the title bar caption to display. Default value
  /// is an empty string.
  /// </param>
  /// <param name="button">
  /// A <see cref="MessageBoxButton"/> value that specifies which button or buttons to
  /// display. Default value is <see cref="MessageBoxButton.OK"/>.
  /// </param>
  /// <param name="icon">
  /// A <see cref="MessageBoxImage"/> value that specifies the icon to display. Default value
  /// is <see cref="MessageBoxImage.None"/>.
  /// </param>
  /// <param name="defaultResult">
  /// A <see cref="MessageBoxResult"/> value that specifies the default result of the
  /// message box. Default value is <see cref="MessageBoxResult.None"/>.
  /// </param>
  /// <returns>
  /// A <see cref="MessageBoxResult"/> value that specifies which message box button is
  /// clicked by the user.
  /// </returns>
  public MessageBoxResult ShowMessageBox(
    INotifyPropertyChanged ownerViewModel,
    string messageBoxText,
    string caption = "",
    MessageBoxButton button = MessageBoxButton.OK,
    MessageBoxImage icon = MessageBoxImage.None,
    MessageBoxResult defaultResult = MessageBoxResult.None)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");

    return MessageBox.Show(
      FindOwnerWindow(ownerViewModel),
      messageBoxText,
      caption,
      button,
      icon,
      defaultResult);
  }

  /// <summary>
  /// Shows the <see cref="OpenFileDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the open file dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  public bool? ShowOpenFileDialog(
    INotifyPropertyChanged ownerViewModel,
    OpenFileDialogSettings settings)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (settings == null)
      throw new ArgumentNullException("settings");

    var dialog = new OpenFileDialogWrapper(settings);
    return dialog.ShowDialog(FindOwnerWindow(ownerViewModel));
  }

  /// <summary>
  /// Shows the <see cref="SaveFileDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the save file dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  public bool? ShowSaveFileDialog(
    INotifyPropertyChanged ownerViewModel,
    SaveFileDialogSettings settings)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (settings == null)
      throw new ArgumentNullException("settings");

    var dialog = new SaveFileDialogWrapper(settings);
    return dialog.ShowDialog(FindOwnerWindow(ownerViewModel));
  }

  /// <summary>
  /// Shows the <see cref="FolderBrowserDialog"/>.
  /// </summary>
  /// <param name="ownerViewModel">
  /// A view model that represents the owner window of the dialog.
  /// </param>
  /// <param name="settings">The settings for the folder browser dialog.</param>
  /// <returns>
  /// If the user clicks the OK button of the dialog that is displayed, true is returned;
  /// otherwise false.
  /// </returns>
  public bool? ShowFolderBrowserDialog(
    INotifyPropertyChanged ownerViewModel,
    FolderBrowserDialogSettings settings)
  {
    if (ownerViewModel == null)
      throw new ArgumentNullException("ownerViewModel");
    if (settings == null)
      throw new ArgumentNullException("settings");

    using (var dialog = new FolderBrowserDialogWrapper(settings))
    {
      DialogResult result = dialog.ShowDialog(new WindowWrapper(FindOwnerWindow(ownerViewModel)));
      return result == DialogResult.OK;
    }
  }

  #endregion

  private void Show(
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel,
    Type dialogType)
  {
    Window dialog = CreateDialog(dialogType, ownerViewModel, viewModel);
    dialog.Show();
  }

  private bool? ShowDialog(
    INotifyPropertyChanged ownerViewModel,
    IModalDialogViewModel viewModel,
    Type dialogType)
  {
    Window dialog = CreateDialog(dialogType, ownerViewModel, viewModel);

    PropertyChangedEventHandler handler = RegisterDialogResult(dialog, viewModel);
    dialog.ShowDialog();
    UnregisterDialogResult(viewModel, handler);

    return viewModel.DialogResult;
  }

  private Window CreateDialog(
    Type dialogType,
    INotifyPropertyChanged ownerViewModel,
    INotifyPropertyChanged viewModel)
  {
    var dialog = dialogFactory(dialogType);
    dialog.Owner = FindOwnerWindow(ownerViewModel);
    dialog.DataContext = viewModel;

    return dialog;
  }

  private static PropertyChangedEventHandler RegisterDialogResult(
    Window dialog,
    IModalDialogViewModel viewModel)
  {
    PropertyChangedEventHandler handler = (sender, e) =>
    {
      if (e.PropertyName == DialogResultPropertyName)
      {
        dialog.DialogResult = viewModel.DialogResult;
      }
    };

    viewModel.PropertyChanged += handler;

    return handler;
  }

  private static void UnregisterDialogResult(
    IModalDialogViewModel viewModel,
    PropertyChangedEventHandler handler)
  {
    viewModel.PropertyChanged -= handler;
  }

  /// <summary>
  /// Finds window corresponding to specified view model.
  /// </summary>
  private static Window FindOwnerWindow(INotifyPropertyChanged viewModel)
  {
    IView view = DialogServiceViews.Views.SingleOrDefault(
      registeredView =>
        registeredView.Source.IsLoaded &&
        ReferenceEquals(registeredView.DataContext, viewModel));

    if (view == null)
      throw new ArgumentException(Resources.ViewModelNotReferenced.CurrentFormat(viewModel.GetType()));

    // Get owner window
    Window owner = view.GetOwner();
    if (owner == null)
      throw new InvalidOperationException(Resources.ViewNotRegistered.CurrentFormat(view.GetType()));

    return owner;
  }
}

GitHub

The code is also available on GitHub. You are welcome to create issues and pull requests.

NuGet

If you want to include MVVM Dialogs in your project, you can install it directly from NuGet.

To install MVVM Dialogs, run the following command in the Package Manager Console:

PM> Install-Package MvvmDialogs

History

  • 24 June 2015: Major code refactoring.
    • Source available on GitHub.
    • Package available as NuGet.
  • 5 October 2010: Code update.
    • Updated source according to comments by d302241.
  • 4 April 2010: Code update.
    • Updated source according to comments by Michael Sync.
    • Converted to .NET 4.
  • 18 June 2009: Code update.
    • Code no longer throws exception in Designer mode.
    • Fixed wrong interface summary.
  • 2 June 2009: Code update.
    • Added the ShowOpenFileDialog method to IDialogService.
    • Implemented a service locator instead of keeping DialogService as a Singleton.
  • 27 May 2009: Article update.
    • Updated introduction after comments from William E. Kempf.
  • 25 May 2009: Initial version.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

Share

About the Author

disore
Software Developer Beijer Electronics
Sweden Sweden
Got my first computer in the 90's and loved it even though it sounded like a coffeemaker.

Now getting paid for designing cool applications, and drinks the coffee instead of listening to it being made.

You may also be interested in...

Comments and Discussions

 
GeneralRe: Require your help on Modal Dialog Pin
Member 93903115-Mar-13 20:59
memberMember 93903115-Mar-13 20:59 
GeneralMy vote of 1 Pin
abdurahman ibn hattab24-Jan-13 10:03
memberabdurahman ibn hattab24-Jan-13 10:03 
GeneralMy vote of 5 Pin
Chad Strawinski18-Jan-13 6:35
memberChad Strawinski18-Jan-13 6:35 
QuestionPretty interesting! Pin
CandyBeat13-Dec-12 3:58
memberCandyBeat13-Dec-12 3:58 
AnswerRe: Pretty interesting! Pin
disore13-Dec-12 11:08
memberdisore13-Dec-12 11:08 
GeneralRe: Pretty interesting! Pin
CandyBeat14-Dec-12 22:55
memberCandyBeat14-Dec-12 22:55 
GeneralRe: Pretty interesting! Pin
disore17-Dec-12 8:21
memberdisore17-Dec-12 8:21 
GeneralMy vote of 5 Pin
karl-barkmann5-Dec-12 15:34
memberkarl-barkmann5-Dec-12 15:34 
good work!
GeneralRe: My vote of 5 Pin
disore5-Dec-12 19:55
memberdisore5-Dec-12 19:55 
QuestionWhat Is The Service For??? Pin
Kevin Marois7-Nov-12 5:31
memberKevin Marois7-Nov-12 5:31 
AnswerRe: What Is The Service For??? Pin
disore7-Nov-12 10:18
memberdisore7-Nov-12 10:18 
GeneralRe: What Is The Service For??? Pin
Kevin Marois7-Nov-12 10:34
memberKevin Marois7-Nov-12 10:34 
GeneralRe: What Is The Service For??? Pin
disore7-Nov-12 11:45
memberdisore7-Nov-12 11:45 
GeneralRe: What Is The Service For??? Pin
Kevin Marois7-Nov-12 12:02
memberKevin Marois7-Nov-12 12:02 
GeneralRe: What Is The Service For??? Pin
disore7-Nov-12 12:34
memberdisore7-Nov-12 12:34 
GeneralRe: What Is The Service For??? Pin
Kevin Marois7-Nov-12 13:03
memberKevin Marois7-Nov-12 13:03 
GeneralRe: What Is The Service For??? Pin
disore7-Nov-12 19:32
memberdisore7-Nov-12 19:32 
GeneralRe: What Is The Service For??? Pin
Kevin Marois8-Nov-12 4:30
memberKevin Marois8-Nov-12 4:30 
GeneralMy vote of 5 Pin
Adminpass20104-Oct-12 5:42
memberAdminpass20104-Oct-12 5:42 
Questionquite disappointed Pin
CodeLinguist4-Oct-12 3:47
memberCodeLinguist4-Oct-12 3:47 
AnswerRe: quite disappointed Pin
disore4-Oct-12 3:57
memberdisore4-Oct-12 3:57 
GeneralRe: quite disappointed Pin
CodeLinguist10-Oct-12 21:05
memberCodeLinguist10-Oct-12 21:05 
GeneralRe: quite disappointed Pin
disore10-Oct-12 21:19
memberdisore10-Oct-12 21:19 
GeneralRe: quite disappointed Pin
CodeLinguist10-Oct-12 21:56
memberCodeLinguist10-Oct-12 21:56 
GeneralRe: quite disappointed Pin
disore10-Oct-12 22:12
memberdisore10-Oct-12 22:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 25 Jun 2015
Article Copyright 2009 by disore
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid