天天看點

《Windows fun 7》一:MVVM for Windows Phone 7

每個Silverlight程式員都有一段痛苦的MVVM經曆,MVVM是Silverlight開發永恒的話題。

完美的概念後面是沒有标準架構,網絡上所能找到的例子也并沒有深入介紹,使用過程中一定會接觸深層次的問題,并且各個開源架構也是實作方式各異,叫我們Silverlight程式員情何以堪?

網絡上一搜MVVM關鍵字,最常出現的就是MSDN上的一篇:http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

但實際這篇隻講述一般應用場景,實際在使用中還有很多問題需要自己解決。

MVVM的概念最早由Josh Smith于2005年提出:http://blogs.msdn.com/b/johngossman/archive/2005/10/08/478683.aspx

網絡上比較完善比較經典的可能是唯一比較全面的教程也是他那本隻有54頁的《Advanced MVVM》,這本Google上能找到PDF連結。

不過,經仔細閱讀和分析之後發現,這個例子仍是講述一般場景,真正開發仍然還有很多問題留給開發者自己解決。

我的安排是首先我們來總結一下到底哪些問題令我們痛苦,然後分析MVVM的起因,最後我們談談怎樣消除這些痛苦。

1.MVVM到底有哪些令人痛苦的問題?

我這裡總結一下,你也可以一起思考,自己有沒有遇到過這些問題,或者正在思考這些問題。

  1. 設計時和運作時支援?XAML需要設計時支援,很多MVVM架構直接将ViewModel定義在Xaml資源中,這不是View引用了ViewModel嗎?
  2. 如果View不能引用ViewModel,那麼View怎樣取得ViewModel作為上下文?有很多架構都是直接在CodeBehind中加載ViewModel對象?
  3. ViewModel的定義好像很亂,标準的ViewModel應該怎樣定義?
  4. ViewModel應該怎樣加載資料?
  5. 在導航的時候,怎樣賦予另一個頁面一個ViewModel上下文?有View來指定ViewModel執行個體還是另外的方式
  6. ViewModel的構造函數應不應該有參數?
  7. 一個方法有多個參數,View應該怎麼樣傳遞這些參數?
  8. View的CodeBehind中應不應該有代碼?
  9. 很多時候,ViewModel中的一個方法都要對應View中的一個UI操作,或則切換狀态,或者修改屬性,ViewModel怎樣觸發UI變化?
  10. ViewModel怎樣調用需要在CodeBehind中執行的方法,比如有些控件的操作不能通過屬性修改,而隻能在CodeBehind中調用這個控件的某個方法,比如彈出一個ChildWindows,或者MessageBox?比如VisualStateManager.GotoState就隻能在CodeBehind中執行?
  11. 上述問題:ViewModel怎樣切換狀态?
  12. 多線程?
  13. Windows Phone 7上的ViewModel怎樣切換多個Application bar?
  14. Windows Phone 7中ViewModel如何處理回退和導航?

看看吧,你有沒有遇到活思考過這些問題?

2.MVVM的由來

其實,這些大多數問題都是有由來的,我們來分析一下。

大多數知道MVVM的人,都可能知道它和MVC有些聯系?然而是什麼聯系和差別?為什麼一般人很容易了解和使用ASP.NET MVC,卻始終不會應用MVVM?可能都說不清楚。

事實上,拿MVC和MVVM比較并沒有多大意義,反而會使人糊塗。在ASP.NET MVC中的View隻承擔顯示資料的作用,它和Controller的互動是通過使用者重新發起一起請求,這個請求和View幾乎沒有關系。并且MVC中得HTML是解釋性的,伺服器端按View的定義,順序解釋,替換掉相應的資料即可。

然而,Xaml卻不是那麼簡單的,Xaml不僅定義了UI(渲染引擎按照Xaml語言的定義計算和排列元素),Xaml更是一棵對象樹,Xaml對象樹的概念十分重要,以至于我們應該時時記住Xaml中的每個節點都是一個對象,這些對象由Xaml解析器執行個體化,并由Silverlight的UI渲染引擎根據布局原理将UI元素繪制到螢幕的相應位置。

可以看到,這和ASP.NET MVC的機制是完全不一樣的,雖然都是基于控件事件模型,但是ASP.NET中對象數執行個體化和顯示是分開的,而Silverlight的UI元素對象樹就是實際顯示在螢幕的UI元素,它們是一個東西,因為Silverlight中這些UI元素和程式的互動和聯系就十分重要和強大。

在Silverlight中,可以說最重要的是引入Xaml程式設計模型,使我們很好和很友善的定義UI元素和對象樹,加上它的Xaml解析器,順序的解析Xaml中的一個一個對象,Xaml解析器通過調用每個節點對應的類型的夠構造函數來構造對象樹,而Xaml能夠識别每個節點的類型,能夠識别像INotifyChanged這樣的類型,是以Silverlight加入依賴屬性,綁定這樣一些概念。這都跟Xaml特點是有關的。Xaml是一種強大的程式設計模型,據悉Xaml團隊已經加入Windows 8 Team,是以可以預見Windows 8的應用程式跟Xaml有很大的關系。

是以,MVVM基本上是Xaml這種程式設計模型催生出來的。它和MVC除了分離的思想外沒有什麼可比的意義。

3.MVVM痛苦的根節點

然而,不管怎麼樣,Silverlight終究還是基于事件模型,事件的程式設計模型是一種極簡的程式設計模式,它讓開發者很容易的處理使用者的輸入(文本或者事件)。不管是Windows Form,Web Form還是Silverlight,都是基于這種程式設計模型。

可是事件程式設計模型有一個很大的缺點,開發者很容易将業務邏輯混在UI操作中,這經常被稱為Codebehind的檔案。這樣的後果是容易使開發者将注意力放到一些表現層的細節上,而且,很容易将業務邏輯與一些表現的細節耦合在一起,更嚴重的是,由于大多數控件對象的不可構造,對程式的可測試性帶來很大挑戰。它也使美工和開發的分工很不明确。

是以,早期有分層的思想,分層實際上就是将某一抽象級别的業務封裝。

而在分層的接近UI層面,也出現分離的思想,它企圖将UI徹底分離出來。MVC即是一種方案,可惜的是VS對ASP.BET MVC的設計時支援不夠好。

而在Silverlight開發中,Blend能支援很好的設計時。

可是問題是,Xaml并不是為MVVM設計的,它的核心思想任然是控件和事件。是以MVVM試圖将View和ViewModel分開,它需要将使用者事件轉化為ViewModel的方法,它需要在ViewModel中驅動一個UI變換,而View有各種各樣的事件和控件,View也有各種各樣的變換。是以原本在Codebehind中輕松搞定的事情,在MVVM模式下需要做大量的事情。我承認Codebehind的方式讓業務有點混亂,但是MVVM實在是很麻煩,有時候你會覺得MVVM實際上把一件事情搞複雜了。

5.MVVM痛苦級别

MVVM的痛苦級别,取決于你想讓Codebehind有多幹淨,這也有需求方面的考量。

1. 如果你僅僅是為了易于測試,業務清晰,這很簡單,你隻要注意把業務盡量移到ViewModel即可,View引用ViewModel都無所謂,在Codebehind中調用ViewModel中方法也無所謂,這實際上跟一般的分層沒多大差別。

2. 如果View和ViewModel都是你在設計,不需要View和ViewModel嚴格分離,你可以大部分使用各個MVVM架構的标準做法,但是,你知道的,每個MVVM架構都有很難完全分離的方面,這個時候可以在CodeBehind中處理,因為轉換到ViewModel中需要增加很大的工作量和了解上的複雜度。這也是我推薦的,或者說這是一個折中。

3. 如果你想完全将View交給另一個你甚至不認識的家夥去設計。你将挑戰最難的MVVM設計--将事件模型完美轉化為MVVM模型,你将不得不去解決所有問題,包括我上面列舉出的一些問題。這樣應該很少。

6.MVVM核心技術和思想

1.附加屬性和Binding

附加屬性是WPF提出的一個全新的屬性系統,它用來支援綁定,變更通知,以及Xaml的樣式Style系統,在MVVM中主要是View和ViewModel之間的資料綁定和傳輸。

2.附加屬性

不可否認,Xaml程式設計模型,最強大和最重要的概念是附加屬性。

Blend中最強大的Behavior,Action,Trigger,VisualState等等,絕大部分特性都和附加屬性有關。要使View和ViewModel完全分開,必須有一層抽象層作為兩者之間的橋梁。

Xaml對于附加屬性的支援,使得這個中間層(附加屬性所屬的類)可以得到一個被附加的對象的引用,這個中間層拿到引用之後就可以做一些翻譯,比如一個ButtonCommand就是一個中間層,它拿到一個Button對象之後,訂閱這個Button的Click事件到一個方法,并讓CanExecute方法執行時,去修改Button的IsEnable屬性。

還有比如你要想在ViewModel控制PhoneApplicationPage的導航和回退,Application Bar的切換等,都可以通過類似的思路來實作。

總之,你記住附加屬性是Silverlight最重要的知識點。

3.上訴情景有時候也可以用擴充方法來代替,這個擴充方法就充當中間層,但是這種情況下要假設這個中間層能夠根據某種規則找到ViewModel對象,并且需要在Codebehind中調用擴充方法。不過這種方式有時候确實有一定的好處。

總之,掌握上述兩點你能應付大部分場景,并且在遇到問題之後可以想辦法去解決,核心思想就是一個中間層,它要麼是利用附加屬性,要麼是利用擴充方法。

繼續閱讀