Providing a confirmation dialog is one of those common requirements that aren’t typically covered when you start reading about the MVVM pattern. Here’s an approach that allows you to still make use of Commands, keep your View Model testable, avoid having to put code behind your View and create a reusable approach for all types of dialogs

imageimage

Even if you’ve defined an ICommand property in your View Model it may still be tempting to simply handle the button’s click event, get a reference to the View Model and wrap calling it depending on the result of a simple MessageBox.

private void deleteButton_Click(object sender, RoutedEventArgs e)
{
    MainViewModel viewModel = DataContext as MainViewModel;
 
    MessageBoxResult result = MessageBox.Show("Are you sure...", "Delete Confirmation", MessageBoxButton.OKCancel);
    if (result == MessageBoxResult.OK)
    {
        viewModel.DeleteCommand.Execute(null);
    }
}

Regardless of how you feel about putting code in your View’s code-behind, this approach, has a few drawbacks.

  • You lose the benefit of binding the Button’s Command and CommandParameter properties – for example, your button won’t automatically disable based on the ICommand.CanExecute property and you have to call Execute explicitly.
  • Your View Model can’t force the user to confirm an action – this is business logic that it should ideally be in control of.
  • You can’t easily test this behaviour.

So here’s another approach – a bit more code but once you’ve set it up it’s about the same amount of effort.

First let’s create an Interface that describes how our View Model might work with a dialog.

public interface IDialogService
{
    int Width { get; set; }
    int Height { get; set; }
    void Show(string title, string message, Action<DialogResult> onClosedCallback);
}

The Show method includes a callback method that has a single parameter, DialogResult, (a simple class I created that contains a boolean Result property) for the result of the dialog confirmation. The callback will be called by any class that implements the IDialogService.

Now, a concrete implementation of this interface could look like this (note I haven’t used a MessageBox dialog this time and have referenced a custom ChildWindow),

public class DialogService: IDialogService
{
    public int Width { get; set; }
    public int Height { get; set; }
 
    public void Show(string title, string message, Action<DialogResult> onClosedCallback)
    {
        // Instantiate a custom ChildWindow that contains a message body that we 
        // can set in the constructor
        ConfirmationDialog dialog = new ConfirmationDialog(message);
 
        // Set the title of the dialog
        dialog.Title = title;
        
        // When the dialog is closed we call the callback (if it exists) with the 
        // result.
        dialog.Closed += (s, e) => 
        {
            if (onClosedCallback != null)
            {
                DialogResult result = new DialogResult();
                result.Result = dialog.DialogResult;
                onClosedCallback(result);
            }
        };
 
        dialog.Width = Width;
        dialog.Height = Height;
        dialog.Show();                
 
    }
}

Adding the IDialogService to our View Model’s constructor allows us to inject a DialogService without the View Model needing to know the details or interact with any user interface.

public class MainViewModel : ViewModelBase
    {
 
    IDialogService DialogService { get; set; }
    public MainViewModel(IDialogService dialogService)
    {
        DialogService = dialogService;
        dialogService.Width = 300;
        dialogService.Height = 200;
    }
    ...
}

It’s then pretty easy to create a Command that requires a confirmation dialog.

DelegateCommand _deleteCommand = null;
public DelegateCommand DeleteCommand
{
    get
    {
        if (_deleteCommand == null)
        {
            _deleteCommand = new DelegateCommand()
            {
                ExecuteAction = (p) =>
                {
                    DialogService.Show(
                        "Delete Confirmation",
                        "Are you sure you want to delete this? There's no turning back",
                        (DialogResult result) =>
                        {
                            if (result.Result.Value)
                            {
                                // You can delete now
                            }
                            else
                            {
                                // Don't delete - abort, abort
                            }
                        }
                    );
                }
            };
        }
        return _deleteCommand;
    }
}

You can now reuse this approach throughout your application.

Easy.

Download Example Files