2015-08-27 27 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