天天看點

雜談MVVM在Silverlight中應用

其實這兩天關于寫這MVVM思路一直很混亂.  一方面也在整理回顧4月份項目中關于MVVM的使用,當時版本用Silverlight 3. 版本4釋出ICommand的支援多少對MVVM應實際應用上産生一定影響. 當然另外一個原因 我在不斷回顧過程也發現不少原來項目中存在的實際問題.這些問題出現同時也給我帶來一些的關于MVVM在Silverlight運用中思考. 我現在依然不能理清寫這篇文章思路. 也有可能整篇文章結構比較混亂, 但大都會針對MVVM在Silverlight項目中運用産生一些實際問題作出整理. 同時在技術群和MSN中每天也有不少Dvp會對MVVM使用提出不少問題. 是以這篇文章打算首先以一個簡單MVVM Demo切入, 然後再對項目中使用MVVM個細節問題點 進行逐個闡述.

本篇會幫助從MVVM了解進入實際編碼入門, 然後對實際運用細節提出我個人看法解決方式[當然有可能是不成熟 也歡迎提出你的見解]. 如果你認為對MVVM運用已經駕輕就熟 敬請飄過. 

<1>為什麼要用MVVM?

關于這個問題 我們把它範圍更縮小一點 單一放到Silverlight中來看.一方面Silverlight帶來很多新的技術體驗動畫,3D 等  另外一方面我們在程式設計上UI層就更加細節化了.可定制.同時新特性Binding、Dependency Property、Routed Events、Command、DataTemplate的不斷出現, 也為原來适用WPF的MVP模式發生轉變, Silverlight需要一個适合自己的架構來"武裝"自己, 新特性的出現加速MVVM基于MVP中演變成型的速度. 這就導緻MVVM從MVP(Model-View-Presenter)模式與WPF結合的應用方式時發展演變過來的一種新型架構架構..

<2>如何運用MVVM?

  既然MVVM出現時一個不斷演變過程, 那麼它的出現回給我們的原來Silverlight編碼産生哪些變化?這個問題我稍後會解釋.先讓我們學會使用這個MVVM.

 <2.1>我們要做什麼?

 為了達到完整示範使用MVVM整個過程,下面我會寫一個小型的DEMO來闡述如何使用MVVM. 在進入編碼前又必要了解一下我們這個程式要做什麼  具體需求如下:

現在有一個場景:

 在工業批量生産中, 會使用多台自動化機器, 而人的作用就是這些自動化機器的管理者. 管理者通過程控機[監控自動化機器運作狀态一種機器] 實時動态采集多台自動化機器的運作狀态參數. 來保證機器正常運作. 說明整個控制流程圖:

<a href="http://blog.51cto.com/attachment/201201/134504238.png" target="_blank"></a>

其實這個是一個簡單單一流程. 反映到程式中 我們就要通過程控機Com通信端口采集多個自動化機器的運作參數并反映目前運作狀況. ok 需求明确了. 那就讓我們用MVVM在Silverlight模拟實作.

&lt;2.2&gt;進入編碼

MVVM具體進行拆分就可以分為M-V-VM 三個層次,M[Model]—V[View]—VM[ViewModel],第一個M代表資料實體Model, View也是UI. 那麼VM來做什麼?當我們有了漂亮View和可操作的Model. 使用者通過View來與程式進行互動.Silverlight提供了我們良好Binding特性, 我們可以把View中控件與Model的特定屬性進行Binding操作, 頁面資料就能直接回報到Model上來, 貌似很好,但是實際運用中我們發現, View中可能出現類型有多個, 而我們的Model定義屬性類型是唯一的,  同時頁面資料需要過濾轉換處理, 這就麻煩了, 我們隻能修改Model, 可是針對多個頁面難道我們修改多次嗎? 那麼也就違反OO程式設計第一原則ISP類單一職責. 不可取.同時這時的Model也變得臃腫龐大, 後期代碼維護代價足以能夠折磨你到"抓狂".

這個時候我們才想起有點冷落的VM了, 我們可以把View除了顯示以外所有活 都交給它來做. 它功能就充分展現了 隔離View與Model直接關聯,同時要滿足View所有向背景請求資料的需求. 備受冷落的VM終于有了用武之地了.清晰流程圖:

<a href="http://blog.51cto.com/attachment/201201/134509650.png" target="_blank"></a>

我們先通過利用程式檢索一下目前機器運作狀态,利用VS2010搭建一個Silverlight Application 結構如下:

<a href="http://blog.51cto.com/attachment/201201/134514527.png" target="_blank"></a>

從結構能清晰看出整體資料流向: 使用者操作View輸入資料,利用Silverlight 4支援的ICommand通過ViewModes通路DataSource資料源, 并把相關資料更新給ViewModel的屬性,View通過TwoWay雙向綁定看到更新資料..

定義View:

有了View我們再來封裝Model, 我們控制對象是機器Machine 封裝成Model:

public class ComMachine       

{           

public string Id { get; set; }           

public string MachineName { get; set; }           

public string State { get; set; }       

緊跟着定義ViewModel:

/// &lt;summary&gt;   

   /// 測試單一MVVM架構資料流向   

   /// Author:chenkai Date:2010年8月10日15:35:43   

   /// &lt;/summary&gt;   

   public class MachineViewModel:INotifyPropertyChanged   

   {   

       #region INotifyPropertyChanged Members   

       public event PropertyChangedEventHandler PropertyChanged;   

       #endregion   

       public MachineViewModel(List&lt;Models.ComMachine&gt; getcuslist)   

       {   

           this.customerlist = getcuslist;   

           this.queryCommand = new Command.QueryDataCommand(this);   

       }   

       //查詢資料   

       private List&lt;Models.ComMachine&gt; customerlist = null;   

       //搜尋關鍵字   

       private string searchKey = string.Empty;   

       public string SearchKey   

           get { return searchKey; }   

           set   

           {    

               searchKey = value;   

               if (PropertyChanged != null)   

                   this.PropertyChanged(this, new PropertyChangedEventArgs("SearchKey"));   

           }   

       //搜尋結果   

       private string searchResult = string.Empty;   

       public string SearchResult   

           get { return searchResult; }   

           {   

               searchResult = value;   

                   this.PropertyChanged(this, new PropertyChangedEventArgs("SearchResult"));   

       //搜尋Command   

       private ICommand queryCommand = null;   

       public ICommand QueryCommand   

           get { return queryCommand; }   

       public void QueryDate()   

           if (!string.IsNullOrEmpty(this.searchKey))   

               Models.ComMachine QueryCustomer = null;   

               foreach (Models.ComMachine getcustomer in DataSource.GetCustomerList())   

               {   

                   if (getcustomer.Id.Equals(this.searchKey))   

                   {   

                       QueryCustomer = getcustomer;   

                       break;   

                   }   

               }   

               if (QueryCustomer != null)   

                   this.SearchResult = "機器名稱:" + QueryCustomer.MachineName + Environment.NewLine + "可用狀态:" + QueryCustomer.State;   

               else 

                   MessageBox.Show("機器編号不存在!");   

                   this.SearchKey = string.Empty;   

           else 

               MessageBox.Show("請輸入你要查詢的機器編号ID!");   

       } 

ViewModel完全為View頁面做的量身定制的類,實作 INotifyPropertyChanged接口當對Viewmodel屬性值發生變更時便進行通知. 另外一個很重要地方是就是當View調用Command指令時,需要在ViewModel中進行關聯.

通過上面我們能看出View中實作的對ViewMode屬性Bingding和對查詢操作Command調用.  也就是說調用Binding和Command是可以寫在XAML中的,而真正定義Binding和Command實作描述的代碼卻在ViewModel中.

Command定義:

/// 封裝指令Command   

/// &lt;/summary&gt;   

public class QueryDataCommand :ICommand   

{   

    private ViewModels.MachineViewModel _CustomerViewModel;   

    public QueryDataCommand(ViewModels.MachineViewModel getcustomer)   

    {   

        this._CustomerViewModel = getcustomer;   

    }   

    //ICommand接口成員之一 确定在目前調用CMd狀态下是否允許其執行方法   

    public bool CanExecute(object parameter)   

        return true;//允許   

    //當出現影響是否應執行該指令的更改時發生   

    public event EventHandler CanExecuteChanged   

        add{ }   

        remove { }   

    public void Execute(object parameter)   

        //調用Command要執行的方法   

        this._CustomerViewModel.QueryDate();   

    } 

SL 3中我們在定義并實作ICommand接口後, 調用往往放在類似事件中需要手動處理.這樣有什麼不好地方?

在沒有結合command patter前,用silverlight進行異步調用的确顯得很簡單,可問題在于如果應用程式再多一些即有多個異步調用的話,我們将就需要添加一些按鈕,而問題也就在于我們不能确定哪個調用屬于哪個按鈕,即行為與UI不能産生直接有效的映射關系,而Silverlight 4Command的全面支援也是完全解除這個調用障礙.

我在技術群中有人曾這樣直接問過我一個Command問題:

當一個Button按鈕有一個Click事件 同時還綁定一個背景調用的Command  當點選按鈕時問他們執行順序? 這個問題當時确實難住了我 平時這樣細節沒有注意.

經過測試發現調用順序是: 先調用Click事件 然後才通過程式對Command解析才調用Command 的Excute方法.

還有一個關于Command調用場景問題:

頁面有一個Button, 當該Button被點選的時候我們要完成一些操作,  将該操作封裝成一個Command并綁定到該Button上就可以了, 但如果我們要在Button被Load的時候執行另外一些操作呢? 

本文轉自chenkaiunion 51CTO部落格,原文連結:http://blog.51cto.com/chenkai/764722