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应该如何关闭表单?