本文的主要內容是自己使用WPF開發以來,本人對相關知識的梳理,僅為個人的總結,對很多事物的理解依然在探索階段,總會有謬誤和更佳的開發方式,如能提出寶貴建議,感激不盡。
雖然本人並不只專注於WPF,還喜歡各類編程語言,但估計在今后1,2年內依然會繼續經常使用WPF開發。
想來想去,時間一長,很多東西還是整理並記錄下來為好,遂決定從發布此文以后,開始慢慢積累,隨着個人知識的擴展和理解,本文可能會不斷添加或者更新內容,然而也會盡可能保留對同一概念前后不同的理解。
WPF搞了快一年,除了去年在上家公司主導開發過一個比較大的WPF項目(已經商業化),其他都是小打小鬧的軟件。在我看來
WPF的優點是:
1. 可以相對比較容易的寫出完全定制化的界面。
2.特有的MVVM設計模式可以完美的分離 UI設計(View層) 和 業務邏輯(Model層)。
WPF的缺點是:
1. 學習成本比較高(我指的是真正精通,你和我說就拖拖控件生成個事件寫個方法,當個處理工具,那都不用學了)
新的概念非常多,容易混淆,本人耐性還是很好的,但依然常常連續好多天不停的折騰,折騰到開始噴MS為何這樣設計。
2. 不能跨平台。過一陣子去研究下Mono。
3. 由於本身的復雜性,BUG有時隱藏的比較深。
4. 坑很多,復雜的項目下很多坑必須得自己跳,官方文檔大多數時候並不能解決實際問題,網上搜索的資料參差不齊,很難查到自己想要的,為了解決大坑最開始常常需要去理解一大堆從天而降的概念。國外下載的demo有時會復雜的過分,csdn上的很多源代碼又太不專業···我就吐槽下。
目前為止,我依然沒有解決的問題:
a. 圖片占用內存太大,在圖片很多並且實時刷新速度很快的時候,如果不寫代碼手動釋放資源,內存就會暴漲至崩潰。而同樣的功能改用Winform實現就只占用很小的內存。
b. 數據虛擬化面板在XP或部分Win7電腦上顯示為空白。只能替換為普通面板。
c. 窗體設置為允許透明時,WebBrowser顯示為空白。(目前查到和嘗試的方案沒有完美解決的)我的辦法是換成第三方瀏覽器內核,比如CEF,但是使用 javascript 內外通信又是很麻煩的事情。不然就是放棄窗體透明···
近期又開始使用WPF寫一個程序,同時希望能利用這次開發,將之前WPF相關的知識做一個整合,重新理解概念,丟棄不好的,探索更加舒服好用的,並將已知的融會貫通,盡可能用最“優雅”的方式去實現功能。
那么我們開始吧。
WPF相關技術一定要弄清楚的知識點,先列個大綱,按照我個人建議的學習順序排序。
1. 理解XAML相關窗體設計的原理。
a. 邏輯樹結構非常類似HTML,但更加麻煩。
b. 可以使用XamlPad查看可視樹結構。
c. 理解Style類似於CSS,並可以通過隨時更換資源字典以達到更換主題或者換膚的目的。
2. 觸發器(Trigger),最常用的是屬性觸發器和數據觸發器。
a. 需要知道觸發器主要是用於視覺交互的。
b. 屬性觸發器是控件本身的某個屬性值發生改變,比如IsMouseOver=True的時候,會觸發可視內容 比如背景色 發生變化。
c. 數據觸發器是在數據模板(DataTemplete)中,當某個業務數據發生變化改變時,會觸發可視內容發生變化。
3. 為了創建形態各異的界面,實現各種神奇的效果,需要學習WPF繪圖。
a. 使用圖形,包括:直線,矩形,橢圓,貝塞爾曲線,Path(最強大的路徑)
b. 應用濾鏡效果,Effect比較簡單,但是導入和開發外部濾鏡,一直沒有研究。
c. 使用變形。有平移,旋轉,縮放,扭曲等基本變形,以及矩陣變形。(要注意的是:每種變形既可以放在呈現變形中,也可以放在布局變形中,需要區分二者的區別。呈現變形只是看到的樣子變化了,實際位置和形狀都沒變。布局變形是真的變化,會在變形的同時不斷對其他控件重新進行布局計算。)
4. 學習使用XAML創建簡單的動畫
a. 嘗試使用3類觸發器觸發動畫的發生
b. 使用VisualStatusManager來應用動畫
c. 如無必要,盡量避免通過寫代碼的方式創建動畫
d.(擴展:使用Blend創建並組合出復雜的動畫。)
5. 依賴屬性和附加屬性。
a. 要學會如何自定義我們自己擴展的依賴屬性和附加屬性。
b. 所謂依賴屬性,從功能上講:就是一個普通的屬性,附帶了可以綁定到任意對象的其他屬性上的功能。所謂綁定就是:一個值變化,另外一個值跟着變化的。這樣可以省去大量的界面效果相關的后台代碼,並使界面和業務代碼分離成為可能。
c. 依賴屬性可用於繼承一個現有控件或者自定義控件,並為其擴展屬性。缺點是這些屬性不能復用,是控件自己專屬的。
d. 附加屬性是一種特殊的依賴屬性。有兩種用法,一種繼承現有類,並進行定義,實現的效果如同Canvas.Left。 一種新建類,主要用於為了不進行大量繼承的前提下,給現有控件擴展額外的屬性。是一種很好的 組合模式 思維。
6. 模板。主要了解:ControlTemplete,DataTemplete
a. ControlTemplete 是用來重寫現有控件的可視結構的,一般和依賴屬性和附加屬性結合,加上綁定,控件可以獲得很好的擴展。
b. DataTemplete 主要用於定義數據對象的可視化結構的。既然是數據對象,最好要有個數據類型,即在DataType中定義。
c. 模板在WPF起着巨大的作用。控件模板可以很容易寫出任意形態任意效果的外觀,數據模板使得View層和ViewModel層很好的分離。請一定要注意,起初我對這樣的概念不屑一顧,其實就是沒明白什么意思。后來我才懂:就因為數據模板的存在,使得代碼中幾乎再也不用出現控件對象了。
d. 推薦模式在模板中的運用,利用自動創建的模板,經常會看到Part_XXX,不明白怎么回事? 看基類的特性中,會有TemplatePart,這其實是一種推薦的設計模式,用於提示后來的開發者,告訴你控件組成的必要元素:名稱和類型。也就是說,你可以重寫控件模板,但是如果要實現控件自身的核心功能,一定要保留一個名為Part_XXX的某個類型的控件才行。
7. MVVM設計模式,最方便學習此模式的是MVVMLight框架,可以直接在NuGet中下載。
a. Model - View - ViewModel。不同於MVC,MVP等設計模式, MVVM最主要的特點是實現UI(View)和業務(Model)的分離。而ViewModel應該同時負責表現邏輯和業務邏輯。這在開發時尤其有用,另外可以同時快速創建設計用的ViewModel,以便設計階段即可以模擬出完全真實的使用效果,因為View層對應的ViewModel可以很容易的切換。
b. 本人並非設計模式的過渡崇拜者,然而只要讓代碼生產力持續保持比較高的效率,就是好的方法。很多時候只要不影響大局,在CodeBehind中寫一些代碼是無可厚非的。
c. MVVMLight框架下,有很好用的EventToCommand,可以將任何事件直接轉化為命令,在我看來這使得UI和業務分離的更加徹底。
d. MVVMLight下的消息機制,Messenger 也是個很好用的東東,可以實現本無關聯的ViewModel間的通信,讓他們繼續無關聯下去。另外也可以用於導航。
e. ViewModel 讓 Model 更加適合於 View。注意兩點:一. ViewModel 不需要知道 View 中有什么,換句話說,ViewModel 中不要引用View中的控件。 二. View 中不要直接引用 Model,而是借助ViewModel,想要用Model怎么做? 比如:PersionViewModel 里有個公有的 Persion 類型的屬性 persion, View 的DataContext是 PersionViewModel,綁定時寫 {Binding Path=persion.Name} 即可。
8. 可以附加在控件上的 行為 (Behavior)
a. 行為,還有上面剛說的 觸發器,EventToCommand,都是一種附加屬性。這樣就很好理解了。通常理解的附加屬性,只是一一個數據類型的值,既然擴展成了一個行為這么復雜的數據類型,目的主要是為了實現各種行為效果的重用,然而行為的封裝是最完整的。
9. 要反復深刻理解裝飾器相關 Adorner, Decorator, AdornerDecorator
a. 從簡單開始,最好懂的是 Decorator,如果這個詞感到陌生,那么Border就不陌生了,邊框嘛,一個東西,外面套個邊而已。
然而Decorator是Border的基類而已。可以擴展它,然而我目前還沒有遇到繼承Decorator的應用場景。
b. 再說Adorner,這才是真正的裝飾器,本身沒有可視結構,它的存在就是要你繼承它,並設計它的可視樣式,如何繼承,網上一大堆教程,最直觀的理解就是:在控件自身上面,蒙了一層額外的裝飾,用於交互性的提示與操作。 比如:光標,旋轉鈕,調整大小,表頭的排序箭頭 等等。需要什么就畫什么,恩恩,這個很重要。
c. 理解了前面兩個,再來說最后一個,看起來好復雜好高大上的樣子, 其實很簡單, 按我的理解: 這就是一個雙面膠,用來粘貼 Decorator 和 Adorner。微軟需要在控件上面有個容器,用來放 Adorner,這個容器最好從控件的最邊緣開始,控件的最邊緣,自然是 Decorator了,OK,那么再包裹一個東西,就可以放 Adorner了, 於是這個東西的名字叫做 AdornerDecorator,上面有個叫做 AdornerLayer的東西可以讓Adorner直接Add。
d. 事實上,只有AdornerDecorator和ScrollContentPresenter具有 AdornerLayer,所以,想要自己加裝飾器的時候,不要忘記在模板里面套一個 AdornerDeocrator 哦。尤其像我這種經常寫自定義窗體的,窗體的 ContentPresenter 外面套個 AdornerDecorator 是 必不能忘的。
10. 路由事件和命令
a. 如果想要寫出來的WPF程序在復雜的界面中不會出現莫名其妙的問題,那么一定要弄懂路由事件和路由命令的概念。
所謂路由,有三種爛大街的方式:隧道,冒泡,直接。 顧名思義:隧道,標簽從外向內響應事件。 冒泡,標簽從內向外相應事件。
直接,只能從定義元素監聽元素的直接事件,寫別的地方沒用。
b. 路由事件是定義在哪一層監聽事件,而不是點擊哪一層會執行事件,目的自然是 “一夫當關萬夫莫開”,比如冒泡路由,N多的控件都可能響應某一鼠標事件,那么只需要在集合控件上面 監聽這一事件就可以了(附加事件)。 在 響應事件處理中,再拿到實際命中的 控件就可以了。 當然,這個時候我往往是直接取命中控件的DataContext,直接轉化為ViewModel,操作數據層。
c. 其實這個路由事件機制和HTML中 javascript 的事件機制非常非常類似,addEventListener的第三個參數 useCapture 用來控制 是否是 Bubbling(冒泡)方式。我也常常會用 jQuery 中的 delegate 來做類似的事情,以便監聽未來產生的子元素相關的事件。
d. 可以在路由事件中標記 Handler = True。來阻止事件繼續路由下去。 然而 實際上 即使這樣設置,路由事件依然在繼續傳遞。 因為用 AddHandler 顯式 掛接路由事件處理程序,可以定義路由事件即使被處理依然會執行。即設置 handledEventsToo = True。 這里分析,相信 Handler || handledEventsToo 為真時,即會執行事件處理程序。
e. 自定義路由事件,類似於注冊依賴屬性。其中可以定義路由類型等等。
f. 說來說去,需要強調的是, Preview名稱開頭的事件是隧道方式,代表了事件是從外向內傳遞的。不帶Preview的事件是冒泡方式,是從內向外傳遞的。 同一名稱的事件Preview先執行。這是個形象的反彈,球彈進去再彈出來,這個過程球可以被人抓住。
g. 路由命令在我看來,因其天生的缺陷與限制,應用的比較少。我一般使用 MVVMLight 的 RelayCommand,當然,如果項目沒必要引入MVVMLight時,自己自定義也可以。
講了半天的理論,都是這不到一年WPF開發的體會,接下來開始實踐內容部分。以后自己實現了感覺不錯的東西,就會列在下面分享。