How should the ViewModel close the form?

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:

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

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


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


    From my perspective the question is pretty good as same approach would be used not only for "Login" window, but for any kind of them. I've passed through a lot of suggestions and no one is ok for me. Please see my kind, that was taken from the MVVM design pattern article.

    Each ViewModel class should be inherited from WorkspaceViewModel that has RequestClose envent, and CloseCommand property of the ICommand type. Default implementation of the CloseCommand property will raise RequestClose event.

    And in order to get window closed the OnLoaded method of your window should be overrided:

    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 are nothing interest in ViewModelBase, it only implements INotifyPropertyChanged interface only
    {
        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
    }
    

    PS Don't treat me badly for those sources, If I had that yesterday that would save me few hours...

    PPS Any comments or suggestions are welcome.


    I used attached behaviours to close the window. Bind a "signal" property on your ViewModel to the attached behaviour (I actually use a trigger) When it's set to true, the behaviour closes the window.

    http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

    链接地址: http://www.djcxy.com/p/20272.html

    上一篇: MVVM

    下一篇: ViewModel应该如何关闭表单?