以下问题是基于这篇文章的评论:MVVM理解问题
我说过这是代码隐藏,这并不违反视图和视图模型分离的关注:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += MainWindow_Closing;
}
void MainWindow_Closing(object sender, CancelEventArgs e)
{
var canExit = ViewModel.ShowConfirmExitDlg();
if (!canExit) e.Cancel = true;
}
}
注释是:
代码隐藏中的任何东西都不能进行单元测试,并且调用对话框的创建是合乎逻辑的,因此不应该在查看
我有两个问题:
- 这是否打破了MVVM分离的关注?
- 你会怎么做(更好)?
我可以使用一些EventTriggers和CallMethod动作从xaml调用viewmodel方法,但这没有任何区别。
我可以使用事件聚合器:
public partial class MainWindow : Window
{
private readonly IEventAggregator _eventAggregator;
public MainWindow(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
InitializeComponent();
Closing += MainWindow_Closing;
}
void MainWindow_Closing(object sender, CancelEventArgs e)
{
var evt = new MainWindowClosingEvent();
_eventAggregator.Publish(evt);
e.Cancel = evt.IsCancel;
}
}
和处理事件在视图模型,但它带来任何价值吗?我仍然不能对取消窗口关闭事件进行单元测试,但是我介绍了同样值得进行单元测试的发布和订阅。这是另一层间接
也许我可以将事件路由到viewmodel:
public MainWindow()
{
InitializeComponent();
Closing += ViewModel.OnWindowClosing;
//or
Closing += (o, e) => ViewModel.OnWindowClosing(e);
}
但是我看不出和原样品有什么不同。
IMHO,视图和视图模型之间的连接不能在视图模型测试中进行单元测试,所以我要么找到一种方法来测试视图,要么是徒劳的。
我认为这里有两个问题。首先,您可以通过使用交互性名称空间和命令来消除一些代码隐藏,有关参考,请参阅:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
ICommand goes here - bind to your VM
</i:EventTrigger>
</i:Interaction.Triggers>
在显示对话框时,需要考虑对话框是视图还是视图模型。当谈到确认窗口关闭时,我认为这是纯粹的观点。因此,您可以在关闭事件的代码后面显示它,而不会破坏MVVM。
关于第一个问题,我是发表评论的人,所以显然我的回答是"是":)
至于第二个,交互触发器是我通常自己实现它的方式,(尽管我也在情况要求时使用附加行为):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="Closed">
<cmd:EventToCommand Command="{Binding ClosedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Close处理程序通过依赖注入框架调用一个对话框的创建,而Close处理程序导致主视图模型自毁:
public ICommand ClosingCommand { get { return new RelayCommand<CancelEventArgs>(OnClosing); } }
private void OnClosing(CancelEventArgs args)
{
#if !DEBUG
var locman = Injector.Get<LocalizationManager>();
var dlg = Injector.Get<CustomDialogViewModel>();
dlg.Caption = locman[LogOffCaption];
dlg.Message = locman[LogOffPrompt];
dlg.OnCancel = (sender) =>
{
args.Cancel = true;
sender.Close();
};
dlg.Show();
#endif
}
public ICommand ClosedCommand { get { return new RelayCommand(OnClosed); } }
private void OnClosed()
{
this.Dispose();
}
这是一个非常简单的例子,但是很明显,通过注入本地化管理器和对话框视图模型的模拟实例,然后直接从测试框架调用命令处理程序,可以很容易地测试这段代码。
值得指出的是,打破纯MVVM并不一定在所有情况下都是坏事。Josh Smith在写关于MVVM的原始文章时似乎非常支持无代码隐藏,但在"高级MVVM"的时候,他似乎采取了更温和的立场,他说"实际的开发人员采取中间道路,并使用良好的判断来确定哪些代码属于哪里"。在我将WPF集成到全栈架构的七八年时间里,我个人从未遇到过纯MVVM不能干净优雅地实现问题的情况,尽管不可否认在某些情况下会增加复杂性。