WPF 是一個界面層框架技術,要對 WPF 技術達到熟練運用的程度,需要同時擁有開發和設計兩方面的知識。而我作為一名開發人員,以前的總結都是站在開發人員的角度,今天這篇博文則期望更多地站在設計人員的角度來進行總結。其實,開發人員比較難理解WPF 框架中為什么會提出 Style、Template、Command、State、StoryBoard、Trigger 等這些概念,但是當你看一看 Flash 或者 PhotoShop 的設計人員平時的工作,就會發現原來許多概念早已是他們的常識,而 .NET 只是把這些概念在 WPF 框架上加以實現而已。
最近接了一個 WPF 的活,對方要求我按照他們美工所畫的圖,使用 WPF 技術構建一模一樣的用戶界面。目前項目已經結束,也收到了約定的勞務費用。由於做得還不錯,所以他們又和我約定了兩個更復雜的項目。其實我個人的 WPF 技術並不高,所以接這個活的一部分原因還是期望通過設計實際的 WPF 項目,來鍛煉自己的 WPF 技術。而本篇博文和之前的 WPF 總結不同,主要是想簡潔地總結一下項目中的 WPF 實戰經驗。也就是說,一是只涉及這個項目中用到的概念,而不是所有 WPF 中的概念;二是不會把某個概念技術說透,只從設計人員的角度去講使用方法。
Template
模板是一個可視化控件結構定義,也就是最終界面顯示的可視樹中控件結構。主要分為兩個,一個是 DataTemplate,一個是 ControlTemplate。
DataTemplate 用於為某一類數據定義可視化控件結構。而 ControlTemplate 則是為某一種類型的邏輯控件定義可視化控件結構。一般情況下,使用 ControlTemplate 的場景要遠遠多過 DataTemplate。
那么如何設計一個 ControlTemplate 中的控件結構呢?其實分兩步,第一步,設計這個控件的靜態結構;第二步,設計控件的動態行為。其實都很簡單,使用 Microsoft Expression Blend 這個專業的 WPF/Silverlight 設計工具進行界面設計,拖拖拽拽就搞定了。
這里要注意的是可視樹中的動態行為。主要有兩種,一種是模板內部根據各可視控件狀態變化而變化的屬性設置,可以直接編寫在 ControlTemplate 的 Triggers 中,Blend 中則可以直接在 Trigger 面板中進行設計;而另一種行為則需要通過與外層邏輯控件的交互完成。交互的方式有:直接綁定邏輯控件屬性、路由命令、路由事件、PART_設計約定。
后三種方式是必須要編寫代碼才能完成的行為。雖然它們並不是設計人員的工作,但是它們是連接開發與設計的橋梁,鑒於它們的重要性,這里還是專門說明一下:
- 路由事件
在設計自定義邏輯控件時,可以在類型的靜態構造器中使用 EventManager.RegisterClassHandler 來處理內部可視樹中所有元素的路由事件。舉個簡單的例子:在 Button 類型的設計代碼中,為 LeftMouseButtonDown 事件注冊了處理函數,並轉換為自己的 Click 事件,這樣,點擊 Button 內部所有可視控件時,才會觸發 Button 的 Click 事件。
這是一種邏輯控件主動去處理或轉換可視控件行為的方式。 - 路由命令
我認為這是一種可視控件主動挑選命令,而邏輯控件被動執行命令調用的方式。
機制是這樣的:控件開發人員為邏輯控件設計了相應的一些行為,但是他們並不知道設計人員會在可視樹中用哪一個具體的元素來執行這個行為。這時,開發人員為邏輯控件編寫一個路由命令,並在類型靜態構造器中為該命令注冊處理函數執行相應的控件邏輯。設計人員則只需要在設計控件模板時,為具體元素設置 Command 即可。這樣,由於命令也是通過路由事件來進行路由的,所以內部的可視樹控件執行命令時,會一直路由到上層的邏輯控件上,並被相應的邏輯處理。達到可視樹控件與邏輯控件交互的效果。 - PART_ 邏輯控件設計約定
當開發一個自定義控件時,如果知道這個控件對應的模板中,必須要有一個某一類型控件,這時我們就可以要求模板設計人員必須在模板中添加該類型的控件,並以一個固定的名稱命名。這樣,開發人員就能在邏輯控件的 ApplyTemplate 方法中通過 Template.Find 找到對應的控件,然后就可以對它進行事件監聽、屬性控制等操作。而連接邏輯控件、模板中可視樹控件的那個名字,為了和一般的命名區分開並顯示其重要性,需要使用“PART_” 起頭。
例如,ComboBox 就在類型設計時,指定了至少需要以下兩個控件,才能發生正常的下拉行為:
Style
樣式本質上是對控件的一組屬性設置集合。
當我們設計好一個 Style 后,可以把它應用到對應控件的許多實例上,那么就算是通過 Style 默認設置好了這些屬性。另外,Style 還提供了 Trigger,可以實現簡單地屬性變更時設置其它屬性的功能。一般較少使用到 EventTrigger。
Style 中我們常常看到的最長的一個屬性設置就是設置 Template 屬性,即控件的模板。雖然他們倆往往出現在一起,但是 Style 跟 Template 其實沒有直接的關系,Style 所做的只是簡單地設置一下控件的 Template 屬性值而已。
有些朋友會問:要達到同樣一個效果,我們也可以在 Template 中直接設置視覺控件的屬性,例如直接設置邊框寬度。那么,為什么還要把一些屬性設置編寫在 Style 中,再去讓 Template 中的控件進行模板綁定,這不是太繞了嗎?其實,這樣做的好處是使得模板中視覺控件的屬性值不會被寫成固定值,可以隨着外層邏輯控件屬性值的變化而變化。這樣,當我們直接給邏輯控件設置邊框寬度時(本地值),模板中的可視控件就會使用這個更高優先級的值來顯示邊框。
自定義控件
在開發實際項目時,一般都會遇到要開發自定義控件的情況。相關內容上面已經都談到了,其實挺簡單的:
- 想好邏輯控件要提供的功能。
- 思考這些功能需要為模板設計人員提供哪些接口,一般是:依賴屬性、路由命令、PART_ 控件約定。(參考上面的 Template 設計。)
- 交互機制確定后,就可以編寫相應的后台邏輯控制代碼 以及 默認的控件樣式(含模板)。
其它 Tips 及小技巧
- Blend 設計界面固然快,但是每次都需要編譯、運行,要看一個效果往往需要多次調整。這時,我們可以使用 snoop 工具來直接調整運行時軟件,當效果達到要求時,再把這些滿意的值調整到 Blend 中。
- 一定要使用 Blend 而不是 VS 來設計界面,除非你對界面沒有一點要求。
- 忘記“我用 VS 也能設計 WPF 界面”這種不切實際的想法吧。我個人就是因為之前有這種想法,導致一直對 WPF 不開竅。我認為這是一個學習 WPF 的誤區,老是以開發人員的思維去思考 WPF。
- 學習 Blend 其實是很簡單的一件事,相比 VS 的學習成本就簡單太多了。如果你要是玩過 Flash、PS,玩起 Blend 來會更快。
- 雖然 Blend 說是給設計人員用的,但是我認為只有開發人員才能真正地用好 Blend,用好 WPF。
- 對於 XAML,不要象 C# 代碼一樣的追求代碼重用。這種東西,Copy 一下改改就可以了。
- Theme 和 Resource:Theme 是主題文件,隨着操作系統的主題變化。在開發自定義控件時會自動生成一個 Theme/Generic.xaml 文件。 可以在 Theme/ 這個文件夾中為不同的操作系統主題設計不同的控件樣式,而找不到相關主題對應的文件時,則會使用 Generic.xaml 文件中的控件樣式。所以:除了自定義控件的樣式需要放到 Theme 中,當某個資源要隨着系統主題變化而變化時,也需要把它編寫到 Theme 文件夾中,否則,應該放到單獨的資源文件中並收入到 Application 中。(這一點是個人的理解,不知道對不對,希望懂的大牛給指點下。)