我试图学习WPF和MVVM问题,但遇到了障碍。
这个问题与这个问题类似,但不完全相同(handling-dialog -in-wpf-with-mvvm)…
我有一个使用MVVM模式编写的“Login”表单。
该表单有一个ViewModel,其中包含用户名和密码,它们使用普通的数据绑定绑定到XAML中的视图。
它还有一个“登录”命令,该命令被绑定到表单上的“登录”按钮,agan使用正常的数据绑定。
当“Login”命令触发时,它会调用ViewModel中的一个函数,该函数会触发并通过网络发送数据以登录。当这个函数完成时,有2个动作:
登录无效-我们只是显示一个消息框,一切正常
登录是有效的,我们需要关闭login表单,并让它返回true作为它的dialgresult…
问题是,ViewModel对实际的视图一无所知,所以它如何关闭视图并告诉它返回一个特定的dialgresult ??我可以在代码背后粘贴一些代码,和/或通过视图模型传递视图,但这似乎会击败整个点MVVM完全…
更新
最后,我违背了MVVM模式的“纯度”,让视图发布了一个Closed事件,并公开了一个Close方法。ViewModel会调用view。close。视图仅通过接口了解,并通过IOC容器连接,因此不会损失可测试性或可维护性。
公认的答案是-5票,这似乎相当愚蠢!虽然我很清楚通过“纯粹”解决问题所获得的良好感觉,但我肯定不是唯一一个认为200行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....
另一个解决方案是在视图模型中使用INotifyPropertyChanged创建属性,如dialgresult,然后在代码背后写:
public class SomeWindow: ChildWindow
{
private SomeViewModel _someViewModel;
public SomeWindow()
{
InitializeComponent();
this.Loaded += SomeWindow_Loaded;
this.Closed += SomeWindow_Closed;
}
void SomeWindow_Loaded(object sender, RoutedEventArgs e)
{
_someViewModel = this.DataContext as SomeViewModel;
_someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
}
void SomeWindow_Closed(object sender, System.EventArgs e)
{
_someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
this.Loaded -= SomeWindow_Loaded;
this.Closed -= SomeWindow_Closed;
}
void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
{
this.DialogResult = _someViewModel.DialogResult;
}
}
}
最重要的片段是_someViewModel_PropertyChanged。
dialgresultpropertyname可以是SomeViewModel中的某个公共const字符串。
我使用这种技巧在视图控件中做一些更改,以防在ViewModel中很难做到这一点。OnPropertyChanged在ViewModel中,你可以在View中做任何你想做的事情。ViewModel仍然是“单元可测试的”,后面代码中的一小行代码没有什么区别。
我实现了Joe White的解决方案,但遇到了偶尔出现的“只有在创建Window并显示为对话框后才能设置dialgresult”错误。
在视图关闭后,我一直保持ViewModel,偶尔我也会使用相同的VM打开一个新的视图。看起来,在旧视图被垃圾收集之前关闭新视图会导致dialgresultchanged试图在关闭的窗口上设置dialgresult属性,从而引发错误。
我的解决方案是改变dialgresultchanged来检查窗口的IsLoaded属性:
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null && window.IsLoaded)
window.DialogResult = e.NewValue as bool?;
}
做出此更改后,关闭对话框的任何附件都将被忽略。
从我的角度来看,这个问题很好,因为同样的方法不仅适用于“Login”窗口,而且适用于任何类型的窗口。我看了很多建议,没有一个适合我。请审阅我从MVVM设计模式文章中获得的建议。
每个ViewModel类都应该继承自WorkspaceViewModel,它具有ICommand类型的RequestClose事件和CloseCommand属性。CloseCommand属性的默认实现将引发RequestClose事件。
为了让窗口关闭,你的窗口的OnLoaded方法应该被重写:
void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
DataContext = customer;
customer.RequestClose += () => { Close(); };
}
你的应用程序的OnStartup方法:
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();
}
我想在WorkspaceViewModel中的RequestClose事件和CloseCommand属性实现是非常清楚的,但我将显示它们是一致的:
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;
}
}
和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.P.S.欢迎任何评论或建议。