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.

29 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.

  4. Another Joe Says:

    Love it!

  5. Ziv Says:

    I’m not sure that when using the MVVM pattern you should use DialogResult.
    In the method that opens the dialog you should initiate the view model than set it to the window DataContext.
    Then, after the window closes just get the data from your view model object.

  6. Joe Says:

    @Ziv, of course you need to do the things you describe. But that’s only the startup code. You left a huge gap between opening the dialog and “after the window closes”. You do realize that WPF doesn’t automatically close your window when you click OK, don’t you? You have to write code to do that — ideally ViewModel code, rather than codebehind.

    The bindable DialogResult solves two problems: it lets your ViewModel say it’s time to close the window, and it lets the caller detect whether the user clicked OK or Cancel. All you’ve said is “don’t use DialogResult”, without clear reasons. What you haven’t done is present an alternate solution to these two problems (or explain why you don’t like this solution).

  7. Lars Jakobsen Says:

    Hi Joe

    Thank you very much for your suggestion, just what I needed. A clarification might be in order though, the Window parameter for your SetDialogResult method, am I supposed to store that as a local variable set by a special constructor for my ViewModel as below or do you have a nicer way of reaching the actual window?

            private readonly PostCommentWindow _window;
    
            public PostCommentCommand PostCommentCommand { get; set; }
    
            public RetroCommentViewModel()
            {
                this.PostCommentCommand = new PostCommentCommand(this);
            }
    
            public RetroCommentViewModel(PostCommentWindow Window)
            {
                _window = Window;
                this.PostCommentCommand = new PostCommentCommand(this);
            }
    
    
            public static readonly DependencyProperty DialogResultProperty =
                DependencyProperty.RegisterAttached("DialogResult", typeof(bool?),
                typeof(RetroCommentViewModel), 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);
            }
    
            public void PostComment()
            {
                Title = Title;
                if (_window != null)
                    SetDialogResult(_window, true);
            }
    

    Best regards
    Lars Jakobsen

  8. Joe Says:

    @Lars: No, no, no. Your ViewModel doesn’t call SetDialogResult. And your ViewModel should never have a reference to the Window. If it does, you’re not doing MVVM, which means this post is of no use to you.

    This is meant to work like anything else in MVVM. Your ViewModel exposes a property that anyone can bind to (and it doesn’t care who might bind to it — it’s just a property). Then you write XAML in your Window to bind the DialogCloser.DialogResult attached property to the ViewModel’s property. That way, when the ViewModel is ready to say “okay, close now,” all it has to do is set its property to true (and do the usual change notification via INotifyPropertyChanged). Because the window has set up the databinding, WPF picks up the change notification and passes the new value along to the DialogCloser’s DialogResult attached property. The ViewModel never talks directly to the window; it just says “I have a property that just changed to true”.

    And nobody ever actually calls SetDialogResult. You *could* call it, in codebehind, if you wanted to write codebehind — but then, again, you’re not doing MVVM. SetDialogResult is actually just a placeholder that’s there to tell WPF, “The DialogResult attached property is writable.” In the same way, GetDialogResult just means “DialogResult is readable.” If you set breakpoints in them, you’ll see they never actually get called.

    Anyway, the ViewModel should never depend directly on the window. That’s what this post is all about — how to break that dependency. If you’re not familiar with the concepts, read up on the Model-View-ViewModel (MVVM) pattern.

  9. Lars Jakobsen Says:

    @Joe

    Thanks again. Epic fail, I can understand :)

    So I might after your correction say as C# newbie it would have been nice to have this little piece below on your blog then. I do get the mvvm pattern, no worries there….its the simple C# I dont get yet….

            private Nullable _dialogResultValue;
    
            public Nullable DialogResult
            {
                get
                {
                    return this._dialogResultValue;
                }
    
                set
                {
                    if (value != this._DialogResultValue)
                    {
                        this._dialogResultValue = value;
                        NotifyPropertyChanged("DialogResult");
                    }
                }
            }
    
            public void PostComment()
            {
                DialogResult = true;
            }
    

    Again thanks, and Best Regards
    Lars J

  10. Yann Says:

    Hi Joe,

    This was VERY useful, thanks for taking the time to publish it. However, having a property called “DialogResult” in my view model made me feel a little uncomfortable, so I just changed the name of it to “Closed”.

    I feel that in the view model it reads a little better to have “Me.Closed = true”, rather than “Me.DialogResult = true”. YMMV

    Great work!

    Thanks again,

    Yann

  11. Rob Says:

    Hi Joe, great bit of code and very easy to use compared to other solutions. I also bumped into the same issue as Mario. My approach may be wrong but I fixed the issue by changing one line of code. In the DialogResultChanged method instead of
    if (window != null)
    I changed it to
    if (window != null && window.IsVisible)
    All appears good and seeing as window.DialogResult can’t be set unless the window is opened, there is no downside to adding the isVisible condition.

    Thanks again

  12. Martin Says:

    I’ve followed your advice, as it sounds like a great way of handling this problem, but I’m still a noob and require some explantation of where and how I set DialogResult? Is it done from the Command of the buttons or do I call SetDialogResult from somewhere?

    A demo project would have helped immensely!

    Please advise,

    Martin

  13. Martin Marchant Says:

    I’m trying the solution above in VB…
    although, the NotifyPropertyChanged is being called correctly in the ViewModel, and updating the ‘DialogResult’ bindable property…

    I’m left with an open form… and no errors?? Is this something you can advise on….

    for other readers, VB Code:

    VIEW:

            xmlns:dsl="clr-namespace:localns"
            dsl:DialogCloser.DialogResult="{Binding DialogResult}"
    

    DIALOGCLOSER:

        Public Class DialogCloser
            Public Shared ReadOnly DialogResultProperty As DependencyProperty = _
                DialogResultProperty.RegisterAttached( _
                                "DialogResult", _
                                GetType(Nullable(Of Boolean)), _
                                GetType(DialogCloser), _
                                New PropertyMetadata(DialogResultProperty))
    
            Private Shared Sub DialogResultChanged( _
                               ByVal d As DependencyObject, _
                               ByVal e As DependencyPropertyChangedEventArgs)
                Dim Item As Window = CType(d, Window)
                If Not IsNothing(Item) Then
                    Item.DialogResult = CType(e.NewValue, Nullable(Of Boolean))
                End If
            End Sub
    
            Public Shared Sub SetDialogResult(ByVal target As Window, ByVal value As Boolean?)
                target.SetValue(DialogResultProperty, value)
            End Sub
        End Class
    

    VIEWMODEL:

           Public Property DialogResult() As Nullable(Of Boolean)
                Get
                    Return Me._dialogResultValue
                End Get
                Set(ByVal value As Nullable(Of Boolean))
                    If (Not value = _dialogResultValue) Then
                        _dialogResultValue = value
                        NotifyPropertyChanged("DialogResult")
                    End If
                End Set
            End Property
    
  14. Fahad Says:

    The DialogResultProperty is static, which means a global variable (a kind of )
    how can I have this in multiple views ?
    If I set one to true, it will close all other views using the same property right ?

  15. Joe Says:

    @Fahad, it sounds like you need to read more about the DependencyProperty pattern before you’re ready to pick apart how this works. But in brief: yes it’s static, but the static variable is readonly and the instance it references is immutable. It doesn’t store the actual property value. It’s just an object instance (kind of like the object instances you create with “new object()” to lock() on — no data, it’s just there to be an instance). That instance is basically used as a key into a dictionary somewhere else.

    Yes, you can use this with multiple views, or even multiple instances of the same view. You drop the DialogCloser into your project once, and then you use it as many times as you want. In each case, you add a property to your ViewModel (and fire INotifyPropertyChanged events when its value changes), and add the one-line binding to your Window, and then all you need to do is set your ViewModel’s property when the ViewModel is ready to close the dialog.

  16. Dio Says:

    Nice! Thanks! =)

  17. Bob Says:

    I sure would appreciate a complete ~simplest~ example including the xaml and viewmodels for a MainWindow and a PopupDialog. Thank you!

    Bob

  18. Keith Says:

    I have extended your class to become a generic Window Closer – which closes a window if it is a normal window (wnd.Show()), but then returns the dialog result if it is a dialog box. (wnd.ShowDialog())

    private static void DialogResultChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
    {
    var window = d as Window;
    if (window != null)
    if (System.Windows.Interop.ComponentDispatcher.IsThreadModal == true)
    window.DialogResult = e.NewValue as bool?;
    else
    window.Close();
    }

  19. TaLz Says:

    same as Bob said.
    a full example would be great.
    also…there is another thing i don’t understand.
    i have a viewmodel and a view (UserControl)
    i want to host the view in a Dialog window.
    this can be done like this:
    view.DataContext = viewModel;
    Window dialog = new Window() { Title = “Branch Account”, Content = view };
    dialog.ShowDialog();

    how do i bind the DialogResult to the parent window?

  20. Joe Says:

    @TaLz:

    Re a “full example”… what do you mean? The post shows you everything you need to do. Add the DialogCloser class to your project, add the binding to your window, and you’re done. (That’s kind of the point.) What more are you looking for?

    As for your viewmodel code, you probably should set the Window’s DataContext, rather than the view’s. The view will inherit its parent’s DataContext, so all your MVVM code will still work; but by having the Window’s DataContext set to the viewmodel, you open the possibility of also binding window properties to the viewmodel (which is exactly what you want to do: you want to bind the DialogCloser.DialogResult property on the Window to a property on your viewmodel).

    If you want to do the binding in code instead of XAML (which would make a lot of sense in this case), you’ll have to look up how to set up databindings programmatically. I don’t remember the exact API.

  21. Mark Says:

    First up – thanks for this sample, it came in very useful for our implementation.

    I personally don’t see a problem with keeping the ViewModel around between calls.

    I can think of a couple of scenarios where you’d want to keep your VM:
    1- when the construction of the ViewModel is expensive,
    2- when you have more than one View associated with the ViewModel.

    Forcing the ViewModel to be destroyed because of a View concern suggests leakage, right?

    In any case the fix seems simple: don’t apply the result to the View if it’s not in a state where it can accept the value from the ViewModel. When the ViewModel loads simply ensure it’s set to NULL again and this resets the DialogResult during load.

    var window = d as Window;
    if (windows != null && window.IsLoaded)
    window.DialogResult = e.NewValue as bool;

  22. John Blood Says:

    This solution is beautiful. I’ve been beating my head against the wall trying to get my popup window to close from the view model. This solution worked great. THANK YOU!

  23. George Says:

    Can you give an example of what the exposed property on the ViewModel would look like?

    Thanks.

  24. Joe Says:

    @George, it’d look just like any other observable property: a property of type bool? with a public getter, and some code that fires the PropertyChanged event when that property’s value changes. If that’s not clear, read any tutorial on the MVVM pattern; Josh Smith’s MVVM tutorial is a good one.

  25. internet Says:

    Hurrah! At last I got a website from where I be able to actually obtain valuable facts regarding my
    study and knowledge.

  26. Brian Says:

    Worked right out of the box. Thanks!

  27. George Yefchak Says:

    Just found this old thread when looking for a VB.net solution. I believe I’ve found two problems with Martin’s VB code above:

    1. The declaration of the DialogResultProperty should include “DependencyProperty.RegisterAttached” rather than “DialogResultProperty.RegisterAttached”
    2. The same declaration should include “PropertyMetaData(AddressOf DialogResultChanged)” rather than “PropertyMetadata(DialogResultProperty)”

    So the complete declaration is:

    Public Shared ReadOnly DialogResultProperty As DependencyProperty = _
    DependencyProperty.RegisterAttached( _
    "DialogResult", _
    GetType(Nullable(Of Boolean)), _
    GetType(DialogCloser), _
    New PropertyMetadata(AddressOf DialogResultChanged))

  28. Behar Says:

    Great Post,
    can you please add a small WPF solution because i’m new in MVVM and i don’t seem to understand it completely.

    retards,
    Behar

  29. Tony Says:

    Hi,

    I am working with VB and the MVVM Light tools. First, thank you George for your posted corrections to Martin’s VB code. I have 1 to add to it. He has in the ViewModel code:

    If (Not value = _dialogResultValue) Then

    That does not work when you set the property value to true. It never sets it. I changed it to:

    If Not value.Equals(_dialogResultValue) Then

    Second, I’m having a problem opening the dialog window the second time. The DialogCloser appears to keep the value in memory and if I open the window a second time I get a “DialogResult can be set only after Window is created and shown as dialog.” exception. Can someone advise on how to fix this? If I take the Shared off the DialogCloser property and methods it won’t build and says DialogCloser does not exist in the namespace. I’m fairly new to WPF and not sure how to get this to create a new instance with the window.

    Thank you!

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-2011. Portions of the site layout use Yahoo! YUI Reset, Fonts, and Grids.
Proudly powered by WordPress. Entries (RSS) and Comments (RSS). Privacy policy