Joe White’s Blog

Life, .NET, and Cats


MVVM and DialogResult with no code-behind

I like the Model-View-ViewModel pattern in WPF, and the way it helps get code out of the UI and into a place you can test it. But every now and then you run into a weird limitation — something you can’t do out of the box. One such example is closing a dialog box.

WPF’s Button doesn’t have a DialogResult property like buttons did in Delphi and WinForms. Instead, the codebehind for your OK button has to manually set the Window’s DialogResult property to true. This makes sense in principle — it lets you validate the user input before you close — but it makes it hard to use “pure” MVVM with no code-behind. I don’t actually give a hoot about blendability (I still write all my own XAML), but since I’m still learning WPF and MVVM, I take it as a challenge to find pure-MVVM solutions to problems, just as a learning exercise.

The obvious (wrong) solution

The obvious solution would be to just do this:

<Window ...
        DialogResult="{Binding DialogResult}">

Then make your ViewModel implement INotifyPropertyChanged in the usual way, and DialogResult gets pushed up to the view the same way as everything else. Right?

Unfortunately, DialogResult isn’t a dependency property (good grief, why not?), so the above code gives you a runtime error when you try to create the window:

A ‘Binding’ cannot be set on the ‘DialogResult’ property of type ‘TestWindow’. A ‘Binding’ can only be set on a DependencyProperty of a DependencyObject.

Back to the drawing board.

Others’ solutions

Some Googling found a StackOverflow post, “how should the ViewModel close the form?”, with an accepted answer (with 5 downvotes) of “give up; you can’t use MVVM for dialog boxes”. But I’m not quite ready to throw in the towel, so I keep reading.

Another answer on the same question — which had 0 upvotes at the time I read it, despite perfectly answering the question — pointed to a blog post by Adam Mills: “Window.Close() from XAML”. Adam’s solution uses an attached behavior. I’m learning to appreciate the attached-behavior pattern; you create an attached property, but then give it side-effects. It’s a good way to get code out of the codebehind, and it forces you to make it reusable at the same time.

But I’m not crazy about the details of Adam’s solution, because it requires you to create a style, hook up triggers, …a lot of mess. His post doesn’t actually have a complete code sample, so I’m not even sure how you hook the style into your window, though I’m sure I could puzzle it out eventually. And even his incomplete example is five lines of XAML. It’d probably be up to 7 or 9 by the time you actually got it fully wired up, and that’s 7 or 9 lines that you have to repeat for every dialog box you write.

Shouldn’t it be simpler? Shouldn’t it be almost as simple as the databinding syntax would have been, if the WPF team had gotten it right and made DialogResult a dependency property?

The one-line* attached behavior

* Okay, yes, it’s two lines if you count the XML namespace.

So I rolled my own attached behavior that does make it almost that simple. Here’s how you use it:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

Your ViewModel should expose a property of type bool? (Nullable<bool>), and should implement INotifyPropertyChanged so it can tell the view when its value has changed.

Here’s the code for DialogCloser:

using System.Windows;
 
namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));
 
        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

I’ve posted this as an answer on the StackOverflow question, so if you think it’s a good solution, feel free to vote it up so that others can find it more easily.

3 Responses to “MVVM and DialogResult with no code-behind”

  1. Links (7/26/2010) « Everything SharePoint/Silverlight Says:

    [...] MVVM and DialogResult with no code-behind [...]

  2. Mario Says:

    Thanks for the solution ;)
    Unfortunately, there’s a problem with it. I have implemented your solution as described, and when I first click on the button that should popup my dialog, the dialog appears. As expected when I click the OK button on it, it closes.
    Here comes the problem. Since I’m using MVVM, a second call to my dialog doesn’t recreate it, but uses the already created instance. Unfortunately, on this instance the dialogresult is already set to true (since I’ve clicked the OK button when I first called it).
    I cannot imagine how I would set the DialogResult back to null before my second call. Anyways, I’m getting an exception and I imagin that the reasion is trying to call ShowDialog with a Dialog having the DialogResult property set to true.

  3. Joe Says:

    @Mario,

    I’m not clear on whether it’s the ViewModel or the View that you’re trying to use multiple times, but in either case, I can’t imagine why you would want to do that.

    If it’s the View (i.e. the Window), then of course you can’t do that with DialogCloser, because you can’t do that without DialogCloser either. If you write plain WPF code to create a Window, call ShowDialog on it, and then call ShowDialog again on the same instance, you’ll get an InvalidOperationException: “Cannot set Visibility or call Show or ShowDialog after window has closed.” It means exactly what it says: if you want to show the dialog again after it’s been closed, you have to create a new instance.

    If it’s the ViewModel that you’re trying to reuse, again, I can’t imagine why. DialogResult won’t be the only property you’ll have to reset to a clean state each time you show the dialog. Why would you want to manually write code to reset the ViewModel to a clean state, when you could just use the features of the runtime the way they were meant to be used? Create a new ViewModel instance each time. It’ll be in a clean state, and it’ll be the same clean state every time. The first time you show the dialog, it’ll behave exactly the same way as the tenth time you show it (not something you can guarantee if you’re trying to reuse the same ViewModel instance over and over).

    A second call to your dialog needs to recreate both the ViewModel and the View. That’s how you show modal UI, whether it’s MVVM or not.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Joe White's Blog copyright © 2004-2008. Portions of the site layout use Yahoo! YUI Reset, Fonts, and Grids.
Proudly powered by WordPress. Entries (RSS) and Comments (RSS).