2015-08-27 29 views
3

以下问题是基于评论在这个帖子:MVVM Understanding IssuesWindow.Closing事件处理程序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; 
    } 
} 

的意见是:

任何在代码隐藏不能被单元测试,并且调用所述 创建的对话框的逻辑,因此不应该在 视图

我有两个问题:

  1. 是否值得关注的问题的这种突破MVVM分离?
  2. 你会怎么做(更好)

我可以打电话给XAML中使用一些EventTriggers和CallMethod动作视图模型的方法,但它并没有任何区别。

我能做到使用事件聚合:

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; 
    } 
} 

并在视图模型处理该事件,但它带来什么价值?我仍然不能单元测试取消窗户关闭事件,但我已经介绍了出版和订阅,这也值得单位推荐。这是间接

的另一层也许我可以路由事件视图模型:

public MainWindow() 
{ 
    InitializeComponent(); 
    Closing += ViewModel.OnWindowClosing; 
    //or 
    Closing += (o, e) => ViewModel.OnWindowClosing(e); 
} 

,但我看不出与原样品太大的区别。

恕我直言,视图和视图模型之间的连接不能在视图模型测试中unittested,所以我要么找到一种方法如何测试观点或它是徒劳无益的。

+0

您是否试图关闭您的viewmodel中的窗口,或者只是传递给您的viewmodel,您的窗口正在关闭? – Cameron

+0

通常,窗口可以通过点击X按钮来关闭,或者按下alt + f4等。 – Liero

回答

1

关于第一个问题,我认为提出的意见的人所以很明显我的回答将是“是的” :)

至于第二,互动触发器是如何我通常实现它自己,(虽然我以前也用过时的情况决定附加的行为):

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

的交割处理程序调用通过依赖注入框架创建一个对话框,而关闭处理器使得主视图模型自毁:

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并不一定是坏事。当他在MVVM上撰写他的原始文章时,Josh Smith似乎非常支持没有代码隐藏的问题,但到“高级MVVM”时代,他似乎采取了一种较为柔和的立场,称“实际开发人员走中间道路,判断确定哪些代码属于哪里“。在我将WPF集成到全堆栈架构的七八年中,我个人从未遇到过纯MVVM无法干净而优雅地实现问题的情况,尽管承认在某些情况下增加了复杂性。你自己的里程会有所不同。

+0

谢谢马克,为解释。你有什么理由特别的原因,你为什么要提供EventToCommand而不是CallMethodAction?对我来说,命令会唤醒某事应该发生,事件处理程序会发生某些事情 – Liero

+0

我可能是错的,但我不认为CallMethodAction允许您传递事件参数,在用户取消时需要'Closing'的情况下?虽然这可能不是最好的例子,但它对于一般的GUI命令来说肯定更有意义,因为它允许您轻松地将命令绑定到别处,而对于CallMethodAction,您必须在处理程序本身中实现该逻辑(当然很容易,但它是另一个不必要的层)。最后是CanExecute的问题;再次,您可以通过其他方式实现,但ICommand支持它即装即用。 –

+1

Just FYI:[CallMethodAction](https://msdn.microsoft.com/en-us/library/ff723947(v = expression.40).aspx):其签名与事件处理程序的签名匹配的公共方法。 – Liero

2

这里有两个问题,就像我看到的那样。首先,你可以通过使用交互的命名空间和命令消除一些代码隐藏,以供参考一下到:

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> 

,当涉及到显示的对话框,你需要考虑的对话框是视图或视图 - 模型。当确认窗户关闭时,我认为这是纯粹的视角。因此,您可以在Closing事件的代码隐藏内部显示,而不需要IMHO打破MVVM。

+0

谢谢您的意见。其实,我不是在代码隐藏中创建对话框,我只是调用viewmodel。 Viewmodel调用DialogService,它可以在测试中被模拟。当谈到EventTriggers时,我已经在我的问题中提到了他们作为 – Liero

+0

没问题。我认为在这种情况下,对于关闭确认对话框,在视图中创建它会很好。在虚拟机中创建它没有任何理由/好处,并且尝试测试关闭确认对话框在我看来似乎过分。所以,你问了两个问题 - 它是否打破了MVVM,并且我会如何做得更好。那么,以上我认为涵盖了这两点。 :) – user3690202