在這裏咱們將將打造一個UserControl(用戶控件)來逐步講解如何在WPF中自定義控件,並將WPF的一些新特性引入到自定義控件中來.
咱們製做了一個帶語音報時功能的鐘表控件, 效果以下:
在VS中右鍵單擊你的項目,點擊"添加新項目",在出現的選擇列表中選擇"UserControl",VS會自動爲你生成一個*.xaml文件以及其對應的後臺代碼文件(*.cs或其它).
值得注意的是,自動生成的代碼中,你的控件是繼承於System.Windows.Controls.UserControl類的,這對應你的控件而言並不必定是最恰當的基類,你能夠修改它,但注意你應該同時修改*.cs文件和*.xaml文件中的基類,而不僅是修改*.cs文件,不然當生成項目時會報錯"不是繼承於同一基類".修改*.xaml文件的方法是:將該文件的第一行和最後一行的"UserControl"改爲與你認爲恰當的基類名稱.
1,爲控件添加屬性(依賴屬性,DependencyProperty)
正以下面的代碼所示:
html
咱們爲控件(或者任何一個WPF類)添加的依賴屬性都是"公開的","靜態的","只讀的",其命名方式是"屬性名+Property",這是依賴屬性一成不變的書寫方式.對於依賴屬性的註冊能夠在聲明該屬性時就調用DependencyProperty.Register()方法註冊,也能夠在其靜態構造方法中註冊.上面的DependencyProperty.Register方法的幾個參數分別是:屬性名(該屬性名與聲明的依賴屬性名稱"XXXProperty"相比僅僅是少了"Property"後綴,其它徹底同樣,不然在運行時會報異常),屬性的數據類型,屬性的擁有者的類型,元數據.
關於參數中傳遞的元數據:若是是普通的類則應該傳遞PropertyMetadata,若是是FrameworkElement則能夠傳遞FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中能夠制定一些標記代表該屬性發生變化時控件應該作出什麼反應,好比某屬性的變化會影響到該控件的繪製,那麼就應該像這樣書寫該屬性的元數據: new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);這樣當該屬性發生變化時系統會考慮重繪該控件.另外元數據中還保護不少內容,好比默認值,數據驗證,數據變化時的回調函數,是否參與屬性"繼承"等.
而後,咱們將該依賴屬性包裝成普通屬性:
函數
GetValue和SetValue方法來自於DependencyObject類,其用於獲取或設置類的某屬性值.
注意:在將依賴屬性包裝成普通屬性時,在get和set塊中除了循序漸進的調用GetValue和SetValue方法外,不要進行任何其它的操做.下面的代碼是不恰當的:
ui
在之前這或許是不少人的慣用寫法,但在WPF中,這樣的寫法存在潛在的錯誤,緣由以下:咱們知道繼承於DependencyObject的類擁有GetValue和SetValue方法來獲取或設置屬性值,那爲何咱們不直接使用該方法來獲取或設置屬性值,而要將其包裝成普通的.NET屬性呢,事實上在這裏兩種方式都是能夠的,只不過包裝成普通的.NET屬性更符合.NET開發人員的習慣,使用GetValue和SetValue更像JAVA開發人員的習慣,但XAML在執行時彷佛於JAVA開發人員同樣,其不會調用.NET屬性而是直接使用GetValue或SetValue方法,這樣一來,咱們寫在get塊和set塊中的其它代碼根本不會被XAML執行到.因此說,就上面的Time屬性而言,C#(或其它)對該屬性的調用不會出現任何問題,但該屬性被用在XAML中時(好比在XAML對該屬性進行數據綁定等),其set塊中的this.OnTimeUpdated(value);語句不會被執行到.
那麼,當Time屬性發生變化時的確須要調用this.OnTimeUpdated(value);語句(由於該語句會引起時間被更新了的事件),仍是在傳遞的依賴屬性元數據作文章:
new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)),咱們爲屬性的變化指定了一個回調函數,當該屬性變化時該回調函數就會被執行:
this
2,爲控件添加事件(傳閱事件,RoutedEvent)
添加傳閱事件的方法與添加依賴屬性的方法很相似:spa
其支持方法EventManager.RegisterRoutedEvent()對應的幾個參數分別爲:事件名稱,事件傳閱的方式(向上傳閱,向下傳閱或不傳閱),事件對應的EventHandler的類型,事件擁有者的類型)
而後將事件包裝成普通的.NET事件:.net
注意,與依賴屬性同樣,不要在add與remove塊中添加除AddHandler與RemoveHandler之外的代碼.
題外話,事件參數中的e.Handled=true並非終止事件的傳閱,這只是爲事件作一個標記而已,以便在默認狀況下的讓那些事件處理函數在該標記爲true的狀況下不被調用,要爲該標記爲true的事件註冊處理方法並讓該方法獲得執行,請使用AddHandler方法,並把最後一個參數handlerEventsToo設置爲true,以下:htm
而後編寫慣用的OnXXX方法:對象
3,爲控件添加命令(Commands)
能爲自定義控件添加如WPF內置控件同樣的命令是一件很不錯的事情(事實上這也是在CustomControl中下降界面和後臺邏輯耦合度的一種方法,本系列隨筆中的下一篇中將會具體談談).
WPF中內置的命令有兩大類型:RoutedCommand以及RoutedUICommand,後者比前者多了一個Text屬性用於在界面上自動本地化地顯示該命令對應的文本,更多的能夠參考WPF中的命令與命令綁定(一)以及WPF中的命令與命令綁定(二).
這裏咱們來定義一個命令,其功能是控件的語音報時.首先咱們定義一個命令:blog
參數分別爲命名的顯示名稱,命令的名稱,命令的擁有者類型.
而後在控件的靜態函數中定義一個命令綁定,該命令綁定定義了命令的具體細節:對應的命令是什麼?其完成什麼樣的功能,當前環境下其能執行嗎?繼承
CanExecuteRoutedEventArgs的CanExecute屬性用於指示當前命令是否可用,也就是說系統會不斷地檢視該命令與該命令的做用對象,並根據你所提供的條件來判斷當前命令是否可用,好比文本框狀態變爲"只讀"後,其"粘貼"命令將不可用,做用於該文本框的粘貼按鈕會自動被禁用,反之則啓用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委託指定了當該命令被執行時所要完成的任務,這經過回調ExcuteSpeak函數來實現.
咱們也能夠爲命令添加快捷鍵,這是經過InputBinding來實現的,其將命令與命令的快捷鍵關聯起來,好比:
這樣,當咱們鼠標點擊控件時就會引起控件的Speak命令,從而調用SpeakTheTime函數進行語音播報.
快捷鍵能夠經過MouseGesture或KeyGesture來定義.
4,優勢與缺點:
正如在在WPF中自定義控件(1) 中談到的同樣,UserControl能比較快速的打造自定義控件,但其對模板樣式等缺少很好的支持,打造出來的控件不如WPF內置控件同樣靈活,在本系列隨筆的下一篇中,咱們將介紹如何打造能對WPF新特性提供徹底支持的CustomControl.
轉自: http://www.cnblogs.com/zhouyinhui/archive/2007/10/27/939920.html 做者:周銀輝