在WPF里使用MVVM開發的時候,似乎總是不可避免的會遇到這樣一個問題:ViewModel在處理完業務之後需要關閉這個Window,這時候要怎麼處理? 網上有很多解決方案:有的在ViewModel拋出一個事件,在View端使用(XXXViewModel)this.DataContext的方式去響應事 ...
在WPF里使用MVVM開發的時候,似乎總是不可避免的會遇到這樣一個問題:ViewModel在處理完業務之後需要關閉這個Window,這時候要怎麼處理?
網上有很多解決方案:有的在ViewModel拋出一個事件,在View端使用(XXXViewModel)this.DataContext的方式去響應事件;有的通過Trigger、Behavior、Action之類的方式曲線救國;還有的使用了其他的第三方框架。
這些操作從某個層面上來說確實能實現這個功能,但是有的操作起來過於麻煩,有的實現功能了但是大大的違反了MVVM的原則,有的則有很多局限性(比如只能針對關閉了Window之後什麼都不做,或者必須要求Window有無參的構造函數)。直到我發現了還可以有這樣一種操作之後,我覺得這應該處理這個問題的最佳實踐了:優雅,簡潔,符合MVVM的思想還沒有局限性。
在MVVM里,View和ViewModel之間通過綁定完成了大部分的操作,這也是MVVM最為推薦的做法。那麼,為什麼View的關閉這個事情不能通過綁定來實現呢?是因為Window沒有控制關閉這個操作的屬性麽?不,在沒有使用MVVM,直接在後臺寫代碼創建了一個Window的時候,我們只需要將這個Window的DialogResult屬性賦值(不管是true還是false)就可以將這個視窗關閉。那麼我們為什麼不直接將Window的DialogResult屬性在ViewModel綁定呢?
秉著這樣的思想我去做了這個實驗,編譯通過,運行的時候得到了這樣的異常提示:
“不能在“ChildWindow”類型的“DialogResult”屬性上設置“Binding”。只能在 DependencyObject 的 DependencyProperty 上設置“Binding”。
這個提示已經很明顯了:為什麼不能直接對Window的DialogResult做綁定,因為DialogResult這個屬性不是依賴屬性,WPF裡面所有的綁定都必須只能綁定依賴屬性,而WPF里絕大部分的屬性都是依賴屬性,但是DialogResult恰恰不是依賴屬性,所以不能綁定。
此路不通之後就有了上面的各種解決方法,但是為什麼不這樣想:DialogResult不是依賴屬性,那我註冊一個依賴屬性不就完了麽?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); } }
然後在View端綁定這個依賴屬性DialogResult:
<Window x:Class="mvvm_demo_close_window.ChildWindow" ... xmlns:xc="clr-namespace:mvvm_demo_close_window" xc:DialogCloser.DialogResult="{Binding DialogResult}">
然後在ViewModel端將這個當做正常的依賴屬性去操作就行了,當this.DialogResult=true的時候就自動在ViewModel關閉了子視窗:
public class ChildWindowViewModel : ViewModelBase { private bool? dialogResult; public bool? DialogResult { get { return this.dialogResult; } set { this.dialogResult = value; RaisePropertyChanged("DialogResult"); } } //用來接收關閉按鈕的Command public ICommand CloseCmd { get { return new DelegateCommand((obj) => { this.DialogResult = true; }); } } }
這是我目前發現的最優雅的解決方案,DialogCloser也完全可以復用,如果大家還有更好的方案,歡迎提出來一起討論。源代碼已在下方給出,需要的自行下載。