1
\$\begingroup\$

I'm looking to solve the problem of setting the Owner property on modal dialogs in WPF, while using IoC and MVVM patterns. I've seen (and written) all sorts of hacks.

To illustrate the problem, here's a naive example of a window with a viewmodel containing an ICommand which prompts the user. It works by having the view set vm.Owner = this, and then the viewmodel using Owner later in a MessageBox when the command is called.

public partial class MyWindow : Window
{
    private readonly MyWindowViewModel vm;

    public MyWindow(MyWindowViewModel vm)
    {
        InitializeComponent();
        DataContext = vm;
        vm.Owner = this;
    }
}

public class MyWindowViewModel
{
    private readonly RelayCommand<object> fooCommand;

    public ICommand FooCommand => fooCommand;
    public Window Owner { get; set; }

    public MyWindowViewModel()
    {
        fooCommand = new RelayCommand<object>(OnFooCommand);
    }

    private void OnFooCommand(object obj)
    {
        if (MessageBox.Show(Owner, "Are you sure?", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
        {
            // ...
        }
    }
}

This works, but the ViewModel is doing View-related things like opening message boxes, and knowing who owns it.

Here's an example that abstracts the dialog into a Func, which is set in the constructor of the Window.

public partial class MyWindow : Window
{
    private readonly MyWindowViewModel vm;

    public MyWindow(MyWindowViewModel vm)
    {
        InitializeComponent();
        DataContext = vm;
        vm.OkCancelBox = OkCancelBox;
    }

    private MessageBoxResult OkCancelBox(string text)
    {
        return MessageBox.Show(this, text, "Confirm", MessageBoxButton.OKCancel, MessageBoxImage.Question);
    }
}

public class MyWindowViewModel
{
    private readonly RelayCommand<object> fooCommand;

    public ICommand FooCommand => fooCommand;
    public Func<string, MessageBoxResult> OkCancelBox { get; set; } = s => throw new NotImplementedException();

    public MyWindowViewModel()
    {
        fooCommand = new RelayCommand<object>(OnFooCommand);
    }

    private void OnFooCommand(object obj)
    {
        if (OkCancelBox("Are you sure?") == MessageBoxResult.OK)
        {
            // ...
        }
    }
}

This feels better, now my ViewModel calls OkCancelBox and gets back a MessageBoxResult, without knowing anything about the View. If you "forget" to set OkCancelBox, you'll get a NotImplementedException. In a test, I can trivially set the Func to simulate the user clicking "OK":

vm.OkCancelBox = s => MessageBoxResult.OK;

Still, I'm not sure I like it.

There are other solutions like passing in (or service-locating) an IMessageBoxService, but I don't fully understand how you can specify the owner of a dialog without the ViewModel knowing who owns it.

Any thoughts on my implementation using Func<string, MessageBoxResult>?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.