Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should the ViewModel close the form?

Tags:

c#

mvvm

wpf

I'm trying to learn WPF and the MVVM problem, but have hit a snag. This question is similar but not quite the same as this one (handling-dialogs-in-wpf-with-mvvm)...

I have a "Login" form written using the MVVM pattern.

This form has a ViewModel which holds the Username and Password, which are bound to the view in the XAML using normal data bindings. It also has a "Login" command which is bound to the "Login" button on the form, agan using normal databinding.

When the "Login" command fires, it invokes a function in the ViewModel which goes off and sends data over the network to log in. When this function completes, there are 2 actions:

  1. The login was invalid - we just show a MessageBox and all is fine

  2. The login was valid, we need to close the Login form and have it return true as its DialogResult...

The problem is, the ViewModel knows nothing about the actual view, so how can it close the view and tell it to return a particular DialogResult?? I could stick some code in the CodeBehind, and/or pass the View through to the ViewModel, but that seems like it would defeat the whole point of MVVM entirely...


Update

In the end I just violated the "purity" of the MVVM pattern and had the View publish a Closed event, and expose a Close method. The ViewModel would then just call view.Close. The view is only known via an interface and wired up via an IOC container, so no testability or maintainability is lost.

It seems rather silly that the accepted answer is at -5 votes! While I'm well aware of the good feelings that one gets by solving a problem while being "pure", Surely I'm not the only one that thinks that 200 lines of events, commands and behaviors just to avoid a one line method in the name of "patterns" and "purity" is a bit ridiculous....

like image 423
Orion Edwards Avatar asked Feb 02 '09 00:02

Orion Edwards


People also ask

How do I close a WPF window?

window. ShowDialog(); Close method of the Window class is used to close a window.


2 Answers

I was inspired by Thejuan's answer to write a simpler attached property. No styles, no triggers; instead, you can just do this:

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

This is almost as clean as if the WPF team had gotten it right and made DialogResult a dependency property in the first place. Just put a bool? DialogResult property on your ViewModel and implement INotifyPropertyChanged, and voilà, your ViewModel can close the Window (and set its DialogResult) just by setting a property. MVVM as it should be.

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 also posted this on my blog.

like image 103
Joe White Avatar answered Oct 12 '22 04:10

Joe White


From my perspective the question is pretty good as the same approach would be used not only for the "Login" window, but for any kind of window. I've reviewed a lot of suggestions and none are OK for me. Please review my suggestion that was taken from the MVVM design pattern article.

Each ViewModel class should inherit from WorkspaceViewModel that has the RequestClose event and CloseCommand property of the ICommand type. The default implementation of the CloseCommand property will raise the RequestClose event.

In order to get the window closed, the OnLoaded method of your window should be overridden:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e) {     CustomerViewModel customer = CustomerViewModel.GetYourCustomer();     DataContext = customer;     customer.RequestClose += () => { Close(); }; } 

or OnStartup method of you app:

    protected override void OnStartup(StartupEventArgs e)     {         base.OnStartup(e);          MainWindow window = new MainWindow();         var viewModel = new MainWindowViewModel();         viewModel.RequestClose += window.Close;         window.DataContext = viewModel;          window.Show();     } 

I guess that RequestClose event and CloseCommand property implementation in the WorkspaceViewModel are pretty clear, but I will show them to be consistent:

public abstract class WorkspaceViewModel : ViewModelBase // There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface {     RelayCommand _closeCommand;     public ICommand CloseCommand     {         get         {             if (_closeCommand == null)             {                 _closeCommand = new RelayCommand(                    param => Close(),                    param => CanClose()                    );             }             return _closeCommand;         }     }      public event Action RequestClose;      public virtual void Close()     {         if ( RequestClose != null )         {             RequestClose();         }     }      public virtual bool CanClose()     {         return true;     } } 

And the source code of the RelayCommand:

public class RelayCommand : ICommand {     #region Constructors      public RelayCommand(Action<object> execute, Predicate<object> canExecute)     {         if (execute == null)             throw new ArgumentNullException("execute");          _execute = execute;         _canExecute = canExecute;     }     #endregion // Constructors      #region ICommand Members      [DebuggerStepThrough]     public bool CanExecute(object parameter)     {         return _canExecute == null ? true : _canExecute(parameter);     }      public event EventHandler CanExecuteChanged     {         add { CommandManager.RequerySuggested += value; }         remove { CommandManager.RequerySuggested -= value; }     }      public void Execute(object parameter)     {         _execute(parameter);     }      #endregion // ICommand Members      #region Fields      readonly Action<object> _execute;     readonly Predicate<object> _canExecute;      #endregion // Fields } 

P.S. Don't treat me badly for those sources! If I had them yesterday that would have saved me a few hours...

P.P.S. Any comments or suggestions are welcome.

like image 31
Budda Avatar answered Oct 12 '22 02:10

Budda