MVVM 模式是一個很久之前的技術了,最近因為一個項目的原因,需要使用 WPF 技術,所以,重新翻出來從前的一段程序,重溫一下當年的技術。
MVVM 模式
MVVM 實際上涉及三個部分,Model, View 和 ViewModel ,三者的關系如下圖所示。
在三部分的關系中,視圖顯示的內容和操作完全依賴於 ViewModel。
Model 是應用程序的核心,代表着最大、最重要的業務資產,因為它記錄了所有復雜的業務實體、它們之間的關系以及它們的功能。
Model 之上是 ViewModel。ViewModel 的兩個主要目標分別是:使 Model 能夠輕松被 WPF/XAML View 使用;將 Model 從 View 分離並對 Model 進行封裝。這些目標當然非常好,但是由於一些現實的原因,有時並不能達到這些目標。
您構建的 ViewModel 知道用戶在高層上將如何與應用程序交互。但是,ViewModel 對 View 一無所知,這是 MVVM 設計模式的重要部分。這使得交互設計師和圖形設計師能夠在 ViewModel 的基礎上創建優美、有效的 UI,同時與開發人員密切配合,設計適當的 ViewModel 來支持其工作。此外,View 與 ViewModel 的分離還使得 ViewModel 更有利於單元測試和重用。
由於視圖模型的變化要影響到視圖的狀態,我們需要使用兩個重要的技術:可觀察對象和命令模式。
可觀察對象
可觀察對象要求當對象的狀態發生變化的時候,需要能夠主動通知所有的觀察者,在 WPF 中涉及到兩個重要的接口 INotifyPropertyChanged 和 INotifyCollectionChanged,它們分別用來表示單個對象的狀態發生了變化,和一個集合發生了變化。
INotifyPropertyChanged 接口的定義如下所示:
namespace System.ComponentModel { // 摘要: // 向客戶端發出某一屬性值已更改的通知。 public interface INotifyPropertyChanged { // 摘要: // 在更改屬性值時發生。 event PropertyChangedEventHandler PropertyChanged; } }
這是一個接口,通常我們會定義一個實現這個接口的基類來便於使用。在下面的實現中,通過事件來通知所有的觀察者。
namespace MVVM.Framework { // 實現觀察者主題的通知 public class BaseObservableObject : INotifyPropertyChanged { // 事件 public event PropertyChangedEventHandler PropertyChanged; // 標准的觸發事件的方法 protected void OnPropertyChanged(string propertyName) { // 如果沒有注冊,會是 null if (PropertyChanged != null) { var e = new PropertyChangedEventArgs(propertyName); PropertyChanged(this, e); } } } }
而 INotifyCollectionChanged 的定義如下,系統已經提供了一個泛型的實現 ObservableCollection,定義在命名空間 System.Collections.ObjectModel 中,我們可以直接使用。
namespace System.Collections.Specialized { // 摘要: // 向偵聽器通知動態更改,如在添加或移除項時或在刷新整個列表時。 [TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public interface INotifyCollectionChanged { // 摘要: // 當集合更改時發生。 event NotifyCollectionChangedEventHandler CollectionChanged; } }
命令模式
對於命令模式來說,最重要的就是我們將每個命令封裝為一個對象,這里涉及的接口是 ICommand,定義如下:
namespace System.Windows.Input { // 摘要: // 定義一個命令 [TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")] [TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")] [ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")] public interface ICommand { // 摘要: // 當出現影響是否應執行該命令的更改時發生。 event EventHandler CanExecuteChanged; // 摘要: // 定義用於確定此命令是否可以在其當前狀態下執行的方法。 // // 參數: // parameter: // 此命令使用的數據。 如果此命令不需要傳遞數據,則該對象可以設置為 null。 // // 返回結果: // 如果可以執行此命令,則為 true;否則為 false。 bool CanExecute(object parameter); // // 摘要: // 定義在調用此命令時調用的方法。 // // 參數: // parameter: // 此命令使用的數據。 如果此命令不需要傳遞數據,則該對象可以設置為 null。 void Execute(object parameter); } }
命令中,不僅包含了執行的方法,還包含了用來判斷是否可以執行的方法,以及當是否可以執行發生變化的事件,這使得我們可以在數據狀態發生變化的時候,動態通知視圖的顯示也同時發生變化,比如,在沒有數據的情況下,修改按鈕是不可用的,當已經存在數據的情況下,修改按鈕就進入可用狀態等等。
通常我們會實現這個接口。我們自己來提供兩個方法分別實現需要執行的操作和判斷是否可以執行的條件。這里涉及到兩個委托。
Predicate 委托表示一個返回 bool 值的方法,這是一個泛型委托,我們可以傳遞一個參數進來,作為判斷的條件。
namespace System { // 摘要: // 表示定義一組條件並確定指定對象是否符合這些條件的方法。 // // 參數: // obj: // 要按照由此委托表示的方法中定義的條件進行比較的對象。 // // 類型參數: // T: // 要比較的對象的類型。 // // 返回結果: // 如果 obj 符合由此委托表示的方法中定義的條件,則為 true;否則為 false。 public delegate bool Predicate<in T>(T obj); }
而 Action 委托則表示一個沒有返回值的方法。我們在 View 中進行操作的時候,通常需要改變的是 ViewModel 的狀態,並不需要返回結果給 View。
namespace System { // 摘要: // 封裝一個方法,該方法只有一個參數並且不返回值。 // // 參數: // obj: // 此委托封裝的方法的參數。 // // 類型參數: // T: // 此委托封裝的方法的參數類型。 public delegate void Action<in T>(T obj); }
這樣,我們默認的命令實現就成為如下的形式,DelegaeCommand 構造函數接收兩個方法,一個就是被封裝的實際操作,一個用來判斷是否可用的方法。
另外額外提供了一個 UpdateCanExecuteState 方法, 在每次執行處理方法之后,自動調用一下,更新是否可用的狀態。
namespace MVVM.Framework { /// <summary> /// 實現命令支持 /// </summary> public class DelegateCommand : ICommand { // 是否可執行的條件 private readonly Predicate<Object> canExecuteMethod; // 實際執行的操作, 表示有一個對象作為參數的方法 private readonly Action<Object> executeActionMethod; // 構造函數 // 創建命令對象的時候,提供實際執行方法的委托 // 判斷是否啟用的委托 public DelegateCommand( Predicate<Object> canExecute, Action<object> executeAction ) { canExecuteMethod = canExecute; executeActionMethod = executeAction; } #region ICommand Members public event EventHandler CanExecuteChanged; // 檢查是否可以執行 public bool CanExecute(object parameter) { var handlers = canExecuteMethod; if (handlers != null) { return handlers(parameter); } return true; } // 執行操作 public void Execute(object parameter) { // 檢查是否提供了實際的方法委托 var handlers = executeActionMethod; if (handlers != null) { handlers(parameter); } // 執行之后,更新是否可以執行的狀態 UpdateCanExecuteState(); } #endregion public void UpdateCanExecuteState() { var handlers = CanExecuteChanged; if (handlers != null) { handlers(this, new EventArgs()); } } } }
第一部分先到這里,后面我們繼續進行。