[转]在WPF中自定义控件 UserControl
在這里我們將將打造一個(gè)UserControl(用戶控件)來逐步講解如何在WPF中自定義控件,并將WPF的一些新特性引入到自定義控件中來.
我們制作了一個(gè)帶語音報(bào)時(shí)功能的鐘表控件, 效果如下:
在VS中右鍵單擊你的項(xiàng)目,點(diǎn)擊"添加新項(xiàng)目",在出現(xiàn)的選擇列表中選擇"UserControl",VS會(huì)自動(dòng)為你生成一個(gè)*.xaml文件以及其對(duì)應(yīng)的后臺(tái)代碼文件(*.cs或其它).
值得注意的是,自動(dòng)生成的代碼中,你的控件是繼承于System.Windows.Controls.UserControl類的,這對(duì)應(yīng)你的控件而言并不一定是最恰當(dāng)?shù)幕?你可以修改它,但注意你應(yīng)該同時(shí)修改*.cs文件和*.xaml文件中的基類,而不只是修改*.cs文件,否則當(dāng)生成項(xiàng)目時(shí)會(huì)報(bào)錯(cuò)"不是繼承于同一基類".修改*.xaml文件的方法是:將該文件的第一行和最后一行的"UserControl"改成與你認(rèn)為恰當(dāng)?shù)幕惷Q.
1,為控件添加屬性(依賴屬性,DependencyProperty)
正如下面的代碼所示:
????????????DependencyProperty.Register("Time",?typeof(DateTime),?typeof(ClockUserCtrl),?
????????????new?FrameworkPropertyMetadata(DateTime.Now,new?PropertyChangedCallback(TimePropertyChangedCallback)));
我們?yōu)榭丶?或者任何一個(gè)WPF類)添加的依賴屬性都是"公開的","靜態(tài)的","只讀的",其命名方式是"屬性名+Property",這是依賴屬性一成不變的書寫方式.對(duì)于依賴屬性的注冊(cè)可以在聲明該屬性時(shí)就調(diào)用DependencyProperty.Register()方法注冊(cè),也可以在其靜態(tài)構(gòu)造方法中注冊(cè).上面的DependencyProperty.Register方法的幾個(gè)參數(shù)分別是:屬性名(該屬性名與聲明的依賴屬性名稱"XXXProperty"相比僅僅是少了"Property"后綴,其它完全一樣,否則在運(yùn)行時(shí)會(huì)報(bào)異常),屬性的數(shù)據(jù)類型,屬性的擁有者的類型,元數(shù)據(jù).
關(guān)于參數(shù)中傳遞的元數(shù)據(jù):如果是普通的類則應(yīng)該傳遞PropertyMetadata,如果是FrameworkElement則可以傳遞FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中可以制定一些標(biāo)記表明該屬性發(fā)生變化時(shí)控件應(yīng)該做出什么反應(yīng),比如某屬性的變化會(huì)影響到該控件的繪制,那么就應(yīng)該像這樣書寫該屬性的元數(shù)據(jù):?new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);這樣當(dāng)該屬性發(fā)生變化時(shí)系統(tǒng)會(huì)考慮重繪該控件.另外元數(shù)據(jù)中還保護(hù)很多內(nèi)容,比如默認(rèn)值,數(shù)據(jù)驗(yàn)證,數(shù)據(jù)變化時(shí)的回調(diào)函數(shù),是否參與屬性"繼承"等.
然后,我們將該依賴屬性包裝成普通屬性:
????????[Category("Common?Properties")]
????????public?DateTime?Time
????????{
????????????get
????????????{
????????????????return?(DateTime)this.GetValue(TimeProperty);
????????????}
????????????set
????????????{
????????????????this.SetValue(TimeProperty,?value);
????????????}
????????}
GetValue和SetValue方法來自于DependencyObject類,其用于獲取或設(shè)置類的某屬性值.
注意:在將依賴屬性包裝成普通屬性時(shí),在get和set塊中除了按部就班的調(diào)用GetValue和SetValue方法外,不要進(jìn)行任何其它的操作.下面的代碼是不恰當(dāng)的:
????????[Category("Common?Properties")]
????????public?DateTime?Time
????????{
????????????get
????????????{
????????????????return?(DateTime)this.GetValue(TimeProperty);
????????????}
????????????set
????????????{
????????????????this.SetValue(TimeProperty,?value);
????????????????this.OnTimeUpdated(value);//Error
????????????}
????????}
在以前這或許是很多人的慣用寫法,但在WPF中,這樣的寫法存在潛在的錯(cuò)誤,原因如下:我們知道繼承于DependencyObject的類擁有GetValue和SetValue方法來獲取或設(shè)置屬性值,那為什么我們不直接使用該方法來獲取或設(shè)置屬性值,而要將其包裝成普通的.NET屬性呢,事實(shí)上在這里兩種方式都是可以的,只不過包裝成普通的.NET屬性更符合.NET開發(fā)人員的習(xí)慣,使用GetValue和SetValue更像JAVA開發(fā)人員的習(xí)慣,但XAML在執(zhí)行時(shí)似乎于JAVA開發(fā)人員一樣,其不會(huì)調(diào)用.NET屬性而是直接使用GetValue或SetValue方法,這樣一來,我們寫在get塊和set塊中的其它代碼根本不會(huì)被XAML執(zhí)行到.所以說,就上面的Time屬性而言,C#(或其它)對(duì)該屬性的調(diào)用不會(huì)出現(xiàn)任何問題,但該屬性被用在XAML中時(shí)(比如在XAML對(duì)該屬性進(jìn)行數(shù)據(jù)綁定等),其set塊中的this.OnTimeUpdated(value);語句不會(huì)被執(zhí)行到.
那么,當(dāng)Time屬性發(fā)生變化時(shí)的確需要調(diào)用this.OnTimeUpdated(value);語句(因?yàn)樵撜Z句會(huì)引發(fā)時(shí)間被更新了的事件),還是在傳遞的依賴屬性元數(shù)據(jù)做文章:
new FrameworkPropertyMetadata(DateTime.Now,new?PropertyChangedCallback(TimePropertyChangedCallback)),我們?yōu)閷傩缘淖兓付艘粋€(gè)回調(diào)函數(shù),當(dāng)該屬性變化時(shí)該回調(diào)函數(shù)就會(huì)被執(zhí)行:
????????{
????????????if?(sender?!=?null?&&?sender?is?ClockUserCtrl)
????????????{
????????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????????clock.OnTimeUpdated((DateTime)arg.OldValue,?(DateTime)arg.NewValue);
????????????????
????????????}
????????}
2,為控件添加事件(傳閱事件,RoutedEvent)
添加傳閱事件的方法與添加依賴屬性的方法很類似:
????????????EventManager.RegisterRoutedEvent("TimeUpdated",
?????????????RoutingStrategy.Bubble,?typeof(RoutedPropertyChangedEventHandler<DateTime>),?typeof(ClockUserCtrl));
其支持方法EventManager.RegisterRoutedEvent()對(duì)應(yīng)的幾個(gè)參數(shù)分別為:事件名稱,事件傳閱的方式(向上傳閱,向下傳閱或不傳閱),事件對(duì)應(yīng)的EventHandler的類型,事件擁有者的類型)
然后將事件包裝成普通的.NET事件:
????????public?event?RoutedPropertyChangedEventHandler<DateTime>?TimeUpdated
????????{
????????????add
????????????{
????????????????this.AddHandler(TimeUpdatedEvent,?value);
????????????}
????????????remove
????????????{
????????????????this.RemoveHandler(TimeUpdatedEvent,?value);
????????????}
????????}
注意,與依賴屬性一樣,不要在add與remove塊中添加除AddHandler與RemoveHandler以外的代碼.
題外話,事件參數(shù)中的e.Handled=true并不是終止事件的傳閱,這只是為事件做一個(gè)標(biāo)記而已,以便在默認(rèn)情況下的讓那些事件處理函數(shù)在該標(biāo)記為true的情況下不被調(diào)用,要為該標(biāo)記為true的事件注冊(cè)處理方法并讓該方法得到執(zhí)行,請(qǐng)使用AddHandler方法,并把最后一個(gè)參數(shù)handlerEventsToo設(shè)置為true,如下:
??????InkCanvas.MouseLeftButtonDownEvent,
??????new?MouseButtonEventHandler(
??????????myInkCanvas_MouseLeftButtonDown),
??????true);
private?void?myInkCanvas_MouseLeftButtonDown(
???????object?sender,?MouseButtonEventArgs?e)
{
???????//do?something
}
然后編寫慣用的OnXXX方法:
????????{
????????????RoutedPropertyChangedEventArgs<DateTime>?arg?=?
????????????????new?RoutedPropertyChangedEventArgs<DateTime>(oldValue,?newValue,TimeUpdatedEvent);
????????????this.RaiseEvent(arg);
????????????
????????}
3,為控件添加命令(Commands)
能為自定義控件添加如WPF內(nèi)置控件一樣的命令是一件很不錯(cuò)的事情(事實(shí)上這也是在CustomControl中降低界面和后臺(tái)邏輯耦合度的一種方法,本系列隨筆中的下一篇中將會(huì)具體談?wù)?.
WPF中內(nèi)置的命令有兩大類型:RoutedCommand以及RoutedUICommand,后者比前者多了一個(gè)Text屬性用于在界面上自動(dòng)本地化地顯示該命令對(duì)應(yīng)的文本,更多的可以參考WPF中的命令與命令綁定(一)以及WPF中的命令與命令綁定(二).?
這里我們來定義一個(gè)命令,其功能是控件的語音報(bào)時(shí).首先我們定義一個(gè)命令:
參數(shù)分別為命名的顯示名稱,命令的名稱,命令的擁有者類型.
然后在控件的靜態(tài)函數(shù)中定義一個(gè)命令綁定,該命令綁定定義了命令的具體細(xì)節(jié):對(duì)應(yīng)的命令是什么?其完成什么樣的功能,當(dāng)前環(huán)境下其能執(zhí)行嗎?
????????????????new?CommandBinding(SpeakCommand,?new?ExecutedRoutedEventHandler(ExecuteSpeak),
????????????????new?CanExecuteRoutedEventHandler(CanExecuteSpeak)); ????????private?static?void?ExecuteSpeak(object?sender,?ExecutedRoutedEventArgs?arg)
????????{
????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????if?(clock?!=?null)
????????????{
????????????????clock.SpeakTheTime();
????????????}
????????}
????????private?static?void?CanExecuteSpeak(object?sender,?CanExecuteRoutedEventArgs?arg)
????????{
????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????arg.CanExecute?=?(clock?!=?null);
????????}
CanExecuteRoutedEventArgs的CanExecute屬性用于指示當(dāng)前命令是否可用,也就是說系統(tǒng)會(huì)不斷地檢視該命令與該命令的作用對(duì)象,并根據(jù)你所提供的條件來判斷當(dāng)前命令是否可用,比如文本框狀態(tài)變?yōu)?#34;只讀"后,其"粘貼"命令將不可用,作用于該文本框的粘貼按鈕會(huì)自動(dòng)被禁用,反之則啟用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委托指定了當(dāng)該命令被執(zhí)行時(shí)所要完成的任務(wù),這通過回調(diào)ExcuteSpeak函數(shù)來實(shí)現(xiàn).
????????{
????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????if?(clock?!=?null)
????????????{
????????????????clock.SpeakTheTime();
????????????}
????????} ????????private?void?SpeakTheTime()
????????{
????????????DateTime?localTime?=?this.Time.ToLocalTime();
????????????string?textToSpeak?=?"現(xiàn)在時(shí)刻,"?+?
????????????????localTime.ToShortDateString()?+","+
????????????????localTime.ToShortTimeString()??+?
????????????????",星期"?+?(int)localTime.DayOfWeek;
????????????this.speecher.SpeakAsync(textToSpeak);
????????}
我們也可以為命令添加快捷鍵,這是通過InputBinding來實(shí)現(xiàn)的,其將命令與命令的快捷鍵關(guān)聯(lián)起來,比如:
????????????InputBinding?inputBinding?=?new?InputBinding(SpeakCommand,?new?MouseGesture(MouseAction.LeftClick));????????????CommandManager.RegisterClassInputBinding(typeof(ClockUserCtrl),?inputBinding);
這樣,當(dāng)我們鼠標(biāo)點(diǎn)擊控件時(shí)就會(huì)引發(fā)控件的Speak命令,從而調(diào)用SpeakTheTime函數(shù)進(jìn)行語音播報(bào).
快捷鍵可以通過MouseGesture或KeyGesture來定義.
4,優(yōu)點(diǎn)與缺點(diǎn):
正如在在WPF中自定義控件(1)?中談到的一樣,UserControl能比較快速的打造自定義控件,但其對(duì)模板樣式等缺乏很好的支持,打造出來的控件不如WPF內(nèi)置控件一樣靈活,在本系列隨筆的下一篇中,我們將介紹如何打造能對(duì)WPF新特性提供完全支持的CustomControl.
DEMO
?
轉(zhuǎn)自:?http://www.cnblogs.com/zhouyinhui/archive/2007/10/27/939920.html 作者:周銀輝
轉(zhuǎn)載于:https://www.cnblogs.com/luohengstudy/p/9888075.html
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的[转]在WPF中自定义控件 UserControl的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。