MVVM探索:從ViewModel關閉Window的最佳實踐


在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也完全可以復用,如果大家還有更好的方案,歡迎提出來一起討論。源代碼已在下方給出,需要的自行下載。

點擊下載源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM