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.
July 26th, 2010 at 7:55 pm
[...] MVVM and DialogResult with no code-behind [...]
August 23rd, 2010 at 8:17 pm
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.
August 24th, 2010 at 11:44 pm
@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.
September 8th, 2010 at 10:04 am
Love it!
September 28th, 2010 at 7:41 am
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.
September 28th, 2010 at 7:48 pm
@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).
October 28th, 2010 at 8:31 am
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
October 28th, 2010 at 5:51 pm
@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.
October 31st, 2010 at 12:19 pm
@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
January 23rd, 2011 at 9:49 pm
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
February 22nd, 2011 at 2:13 am
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
April 12th, 2011 at 9:12 am
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
May 16th, 2011 at 11:00 am
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 ClassVIEWMODEL:
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 PropertyJune 9th, 2011 at 10:09 am
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 ?
June 10th, 2011 at 10:05 pm
@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.
December 2nd, 2011 at 7:48 pm
Nice! Thanks! =)
January 25th, 2012 at 8:01 am
I sure would appreciate a complete ~simplest~ example including the xaml and viewmodels for a MainWindow and a PopupDialog. Thank you!
Bob