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 中。(這一點是個人的了解,不知道對不對,希望懂的大牛給指點下。)