【转】在WPF中自定义控件
周銀輝的開發(fā)博客(WPF)
在WPF中自定義控件(1)
一, 不一定需要自定義控件
在使用WPF以前,動輒使用自定義控件幾乎成了慣性思維,比如需要一個帶圖片的按鈕,但在WPF中此類任務(wù)卻不需要如此大費周章,因為控件可以嵌套使用以及可以為控件外觀打造一套新的樣式就可以了.是否需要我們來自定義控件,這需要你考慮目前已有控件的真正邏輯功能而不要局限于外觀,如果目前的控件都不能直覺地表達(dá)你的想法,那么你可以自己來打造一個控件,否則,也許我們僅僅改變一下目前控件的模板等就可以完成任務(wù).很多人在自定義控件上經(jīng)常犯的錯誤是:重復(fù)撰寫已有的邏輯
二,UserControl還是CustomControl?
要在WPF中自定義一個控件,使用UserControl與CustomControl都是不錯的選擇(除此之外,還有更多選擇,比如打造一個自定義的面板,但這不在本文的討論范圍),他們的區(qū)別在于:
UserControl,其更像WinForm中自定義控件的開發(fā)風(fēng)格,在開發(fā)上更簡單快速,幾乎可以簡單地理解為:利用設(shè)計器來將多個已有控件作為子元素來拼湊成一個UserControl并修改其外觀,然后后臺邏輯代碼直接訪問這些子元素.其最大的弊端在于:其對模板樣式等支持度不好,其重復(fù)使用的范圍有限.
CustomControl, 其開發(fā)出來的控件才真正具有WPF風(fēng)格,其對模板樣式有著很好的支持,這是因為打造CustomControl時做到了邏輯代碼與外觀相分離,即使換上一套完全不同的視覺樹其同樣能很好的工作,就像WPF內(nèi)置的控件一樣.
在使用Visual Studio打造控件時,UserControl與CustomControl的差別就更加明顯,在項目中添加一個UserControl時,我們會發(fā)現(xiàn)設(shè)計器為我們添加了一個XAML文件以及一個對應(yīng)的.CS文件(或.VB等),然后你就可以像設(shè)計普通窗體一樣設(shè)計該UserControl; 如果我們是在項目中添加一個CustomControl,情況卻不是這樣,設(shè)計器會為我們生成一個.CS文件(或.VB等),該文件用于編寫控件的后臺邏輯,而控件的外觀卻定義在了軟件的應(yīng)用主題(Theme)中了(如果你沒有為軟件定義通用主題,其會自動生成一個通用主題themes\generic.xaml, 然后主題中會自動為你的控件生成一個Style),并將通用主題與該控件關(guān)聯(lián)了起來.這也就是CustomControl對樣式的支持度比UserControl好的原因.
三,繼承于UserContorl,Control還是其它?
如果你準(zhǔn)備打造一個控件,并使用像Visual?Studio這樣的工具來開發(fā)的話,打造UserControl時其會自動為你從System.Windows.Controls.UserControl繼承,打造CustomControl時其會為從System.Windows.Controls.Control繼承.但實際情況下,也許我們從他們的衍生類別開始繼承會得到更多的好處(更好的重用已有的邏輯),比如你的控件擁有更多的類似于Button的某些特性,那么從Button開始繼承就比從Control繼承少寫很多代碼.
在接下來的幾節(jié)中,我們會逐步討論如何打造UserControl與CustomControl以及讓它們更好支持WPF新特性.
?
在WPF中自定義控件(2) UserControl
?
在這里我們將將打造一個UserControl(用戶控件)來逐步講解如何在WPF中自定義控件,并將WPF的一些新特性引入到自定義控件中來.
我們制作了一個帶語音報時功能的鐘表控件, 效果如下:
在VS中右鍵單擊你的項目,點擊"添加新項目",在出現(xiàn)的選擇列表中選擇"UserControl",VS會自動為你生成一個*.xaml文件以及其對應(yīng)的后臺代碼文件(*.cs或其它).
值得注意的是,自動生成的代碼中,你的控件是繼承于System.Windows.Controls.UserControl類的,這對應(yīng)你的控件而言并不一定是最恰當(dāng)?shù)幕?你可以修改它,但注意你應(yīng)該同時修改*.cs文件和*.xaml文件中的基類,而不只是修改*.cs文件,否則當(dāng)生成項目時會報錯"不是繼承于同一基類".修改*.xaml文件的方法是:將該文件的第一行和最后一行的"UserControl"改成與你認(rèn)為恰當(dāng)?shù)幕惷Q.
1,為控件添加屬性(依賴屬性,DependencyProperty)
正如下面的代碼所示:
?public???static???readonly??DependencyProperty?TimeProperty??=??
????????????DependencyProperty.Register(?"?Time?"?,??typeof?(DateTime),??typeof?(ClockUserCtrl),?
?????????????new??FrameworkPropertyMetadata(DateTime.Now,?new??PropertyChangedCallback(TimePropertyChangedCallback)));
我們?yōu)榭丶?或者任何一個WPF類)添加的依賴屬性都是"公開的","靜態(tài)的","只讀的",其命名方式是"屬性名+Property",這是依賴屬性一成不變的書寫方式.對于依賴屬性的注冊可以在聲明該屬性時就調(diào)用?DependencyProperty.Register()方法注冊,也可以在其靜態(tài)構(gòu)造方法中注冊.上面的?DependencyProperty.Register方法的幾個參數(shù)分別是:屬性名(該屬性名與聲明的依賴屬性名稱"XXXProperty"相比僅僅是少了"Property"后綴,其它完全一樣,否則在運行時會報異常),屬性的數(shù)據(jù)類型,屬性的擁有者的類型,元數(shù)據(jù).
關(guān)于參數(shù)中傳遞的元數(shù)據(jù):如果是普通的類則應(yīng)該傳遞?PropertyMetadata,如果是FrameworkElement則可以傳遞?FrameworkPropertyMetadata,其中?FrameworkPropertyMetadata中可以制定一些標(biāo)記表明該屬性發(fā)生變化時控件應(yīng)該做出什么反應(yīng),比如某屬性的變化會影響到該控件的繪制,那么就應(yīng)該像這樣書寫該屬性的元數(shù)據(jù):??new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);這樣當(dāng)該屬性發(fā)生變化時系統(tǒng)會考慮重繪該控件.另外元數(shù)據(jù)中還保護(hù)很多內(nèi)容,比如默認(rèn)值,數(shù)據(jù)驗證,數(shù)據(jù)變化時的回調(diào)函數(shù),是否參與屬性"繼承"等.
然后,我們將該依賴屬性包裝成普通屬性:
?????????[Description(?"?獲取或設(shè)置當(dāng)前日期和時間?"?)]
????????[Category(?"?Common?Properties?"?)]
?????????public??DateTime?Time
?????????{
????????????get
????????????{
????????????????return?(DateTime)this.GetValue(TimeProperty);
????????????}
????????????set
????????????{
????????????????this.SetValue(TimeProperty,?value);
????????????}
????????}
GetValue和SetValue方法來自于DependencyObject類,其用于獲取或設(shè)置類的某屬性值.
注意:在將依賴屬性包裝成普通屬性時,在get和set塊中除了按部就班的調(diào)用GetValue和SetValue方法外,不要進(jìn)行任何其它的操作.下面的代碼是?不恰當(dāng)的:
?????????[Description(?"?獲取或設(shè)置當(dāng)前日期和時間?"?)]
????????[Category(?"?Common?Properties?"?)]
?????????public??DateTime?Time
?????????{
????????????get
????????????{
????????????????return?(DateTime)this.GetValue(TimeProperty);
????????????}
????????????set
????????????{
????????????????this.SetValue(TimeProperty,?value);
????????????????this.OnTimeUpdated(value);//Error
????????????}
????????}
在以前這或許是很多人的慣用寫法,但在WPF中,這樣的寫法存在潛在的錯誤,原因如下:我們知道繼承于DependencyObject的類擁有GetValue和SetValue方法來獲取或設(shè)置屬性值,那為什么我們不直接使用該方法來獲取或設(shè)置屬性值,而要將其包裝成普通的.NET屬性呢,事實上在這里兩種方式都是可以的,只不過包裝成普通的.NET屬性更符合.NET開發(fā)人員的習(xí)慣,使用GetValue和SetValue更像JAVA開發(fā)人員的習(xí)慣,但XAML在執(zhí)行時似乎于JAVA開發(fā)人員一樣,其不會調(diào)用.NET屬性而是直接使用GetValue或SetValue方法,這樣一來,我們寫在get塊和set塊中的其它代碼根本不會被XAML執(zhí)行到.所以說,就上面的Time屬性而言,C#(或其它)對該屬性的調(diào)用不會出現(xiàn)任何問題,但該屬性被用在XAML中時(比如在XAML對該屬性進(jìn)行數(shù)據(jù)綁定等),其set塊中的?this.OnTimeUpdated(value);語句不會被執(zhí)行到.
那么,當(dāng)Time屬性發(fā)生變化時的確需要調(diào)用?this.OnTimeUpdated(value);語句(因為該語句會引發(fā)時間被更新了的事件),還是在傳遞的依賴屬性元數(shù)據(jù)做文章:
new FrameworkPropertyMetadata(DateTime.Now,new?PropertyChangedCallback(TimePropertyChangedCallback)),我們?yōu)閷傩缘淖兓付艘粋€回調(diào)函數(shù),當(dāng)該屬性變化時該回調(diào)函數(shù)就會被執(zhí)行:
??????????private???static???void??TimePropertyChangedCallback(DependencyObject?sender,?DependencyPropertyChangedEventArgs?arg)
?????????{
????????????if?(sender?!=?null?&&?sender?is?ClockUserCtrl)
????????????{
????????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????????clock.OnTimeUpdated((DateTime)arg.OldValue,?(DateTime)arg.NewValue);
????????????????
????????????}
????????}
2,為控件添加事件(傳閱事件,RoutedEvent)
添加傳閱事件的方法與添加依賴屬性的方法很類似:
??????????public???static???readonly??RoutedEvent?TimeUpdatedEvent??=??
????????????EventManager.RegisterRoutedEvent(?"?TimeUpdated?"?,
?????????????RoutingStrategy.Bubble,??typeof?(RoutedPropertyChangedEventHandler?<?DateTime?>?),??typeof?(ClockUserCtrl));
其支持方法?EventManager.RegisterRoutedEvent()對應(yīng)的幾個參數(shù)分別為:事件名稱,事件傳閱的方式(向上傳閱,向下傳閱或不傳閱),事件對應(yīng)的EventHandler的類型,事件擁有者的類型)
然后將事件包裝成普通的.NET事件:
?????????[Description(?"?日期或時間被更新后發(fā)生?"?)]
?????????public???event??RoutedPropertyChangedEventHandler?<?DateTime?>??TimeUpdated
?????????{
????????????add
????????????{
????????????????this.AddHandler(TimeUpdatedEvent,?value);
????????????}
????????????remove
????????????{
????????????????this.RemoveHandler(TimeUpdatedEvent,?value);
????????????}
????????}
注意,與依賴屬性一樣,不要在add與remove塊中添加除AddHandler與RemoveHandler以外的代碼.
題外話,事件參數(shù)中的e.Handled=true并不是終止事件的傳閱,這只是為事件做一個標(biāo)記而已,以便在默認(rèn)情況下的讓那些事件處理函數(shù)在該標(biāo)記為true的情況下不被調(diào)用,要為該標(biāo)記為true的事件注冊處理方法并讓該方法得到執(zhí)行,請使用AddHandler方法,并把最后一個參數(shù)handlerEventsToo設(shè)置為true,如下:
?this?.myInkCanvas.AddHandler(
??????InkCanvas.MouseLeftButtonDownEvent,
???????new??MouseButtonEventHandler(
??????????myInkCanvas_MouseLeftButtonDown),
???????true?);
?private???void??myInkCanvas_MouseLeftButtonDown(
????????object??sender,?MouseButtonEventArgs?e)
?{
???????//do?something
}
然后編寫慣用的OnXXX方法:
??????????protected???virtual???void??OnTimeUpdated(DateTime?oldValue,?DateTime?newValue)
?????????{
????????????RoutedPropertyChangedEventArgs<DateTime>?arg?=?
????????????????new?RoutedPropertyChangedEventArgs<DateTime>(oldValue,?newValue,TimeUpdatedEvent);
????????????this.RaiseEvent(arg);
????????????
????????}
?
3,為控件添加命令(Commands)
能為自定義控件添加如WPF內(nèi)置控件一樣的命令是一件很不錯的事情(事實上這也是在CustomControl中降低界面和后臺邏輯耦合度的一種方法,本系列隨筆中的下一篇中將會具體談?wù)?.
WPF中內(nèi)置的命令有兩大類型:RoutedCommand以及RoutedUICommand,后者比前者多了一個Text屬性用于在界面上自動本地化地顯示該命令對應(yīng)的文本,更多的可以參考WPF中的命令與命令綁定(一)以及WPF中的命令與命令綁定(二).
這里我們來定義一個命令,其功能是控件的語音報時.首先我們定義一個命令:
??????????public???static???readonly??RoutedUICommand?SpeakCommand??=???new??RoutedUICommand(?"?Speak?"?,??"?Speak?"?,??typeof?(ClockUserCtrl));
參數(shù)分別為命名的顯示名稱,命令的名稱,命令的擁有者類型.
然后在控件的靜態(tài)函數(shù)中定義一個命令綁定,該命令綁定定義了命令的具體細(xì)節(jié):對應(yīng)的命令是什么?其完成什么樣的功能,當(dāng)前環(huán)境下其能執(zhí)行嗎?
?????????????CommandBinding?commandBinding??=
?????????????????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)會不斷地檢視該命令與該命令的作用對象,并根據(jù)你所提供的條件來判斷當(dāng)前命令是否可用,比如文本框狀態(tài)變?yōu)?#34;只讀"后,其"粘貼"命令將不可用,作用于該文本框的粘貼按鈕會自動被禁用,反之則啟用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委托指定了當(dāng)該命令被執(zhí)行時所要完成的任務(wù),這通過回調(diào)ExcuteSpeak函數(shù)來實現(xiàn).
??????????private???static???void??ExecuteSpeak(?object??sender,?ExecutedRoutedEventArgs?arg)
?????????{
????????????ClockUserCtrl?clock?=?sender?as?ClockUserCtrl;
????????????if?(clock?!=?null)
????????????{
????????????????clock.SpeakTheTime();
????????????}
????????}
??????????private???void??SpeakTheTime()
?????????{
????????????DateTime?localTime?=?this.Time.ToLocalTime();
????????????string?textToSpeak?=?"現(xiàn)在時刻,"?+?
????????????????localTime.ToShortDateString()?+","+
????????????????localTime.ToShortTimeString()??+?
????????????????",星期"?+?(int)localTime.DayOfWeek;
????????????this.speecher.SpeakAsync(textToSpeak);
????????}
我們也可以為命令添加快捷鍵,這是通過InputBinding來實現(xiàn)的,其將命令與命令的快捷鍵關(guān)聯(lián)起來,比如:
?????????????InputBinding?inputBinding??=???new??InputBinding(SpeakCommand,??new??MouseGesture(MouseAction.LeftClick));
????????????CommandManager.RegisterClassInputBinding(?typeof?(ClockUserCtrl),?inputBinding);
這樣,當(dāng)我們鼠標(biāo)點擊控件時就會引發(fā)控件的Speak命令,從而調(diào)用SpeakTheTime函數(shù)進(jìn)行語音播報.
快捷鍵可以通過MouseGesture或KeyGesture來定義.
4,優(yōu)點與缺點:
正如在在WPF中自定義控件(1)?中談到的一樣,UserControl能比較快速的打造自定義控件,但其對模板樣式等缺乏很好的支持,打造出來的控件不如WPF內(nèi)置控件一樣靈活,在本系列隨筆的下一篇中,我們將介紹如何打造能對WPF新特性提供完全支持的CustomControl.
DEMO
?
WPF中的命令與命令綁定(一)
說到用戶輸入,可能我們更多地會聯(lián)想到鍵盤、鼠標(biāo)、手寫筆,其實還用一種高級別的輸入——命令(Commands),從WPF類庫角度講他們分別對于Keyboard,Mouse,Ink與ICommand。命令是一種語義級別的輸入而不是設(shè)備級別的,比如“復(fù)制”與“粘貼”,但實現(xiàn)一個命令可以有很多中方式,比如“粘貼”,我們可以使用CTRL-V,也可以使用主菜單或右鍵菜單(上下文菜單)等等。在以往的.net版本中,要在軟件界面上添加一個“粘貼”按鈕,是非常麻煩的事情,你得監(jiān)視剪切板中是否有可用的文本以及對應(yīng)的文本框是否獲得了焦點以便啟用或禁用該按鈕,當(dāng)粘貼時你還得從剪切板中取得相應(yīng)的文本并插入到文本框的合理位置,等等。
在WPF中提供的命令機制能非常簡單地實現(xiàn)這些任務(wù),下面的Demo演示了如何簡單到不用手動編寫一行后臺邏輯代碼便解決上面的難題的,你可以粘貼下面的代碼到XamlPad:
?<?Window
?????xmlns?="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
????xmlns:x?="http://schemas.microsoft.com/winfx/2006/xaml"
????x:Name?="Window"
????Title?="Window1"
????Width?="640"??Height?="480"?>
?????<?DockPanel??LastChildFill?="True"?>
?????????<?Menu??Width?="Auto"??Height?="20"??DockPanel.Dock?="Top"?>
?????????????<?MenuItem??Command?="ApplicationCommands.Copy"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?????????????<?MenuItem??Command?="ApplicationCommands.Paste"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?????????????<?MenuItem??Command?="ApplicationCommands.Cut"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?????????????<?MenuItem??Command?="ApplicationCommands.Redo"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?????????????<?MenuItem??Command?="ApplicationCommands.Undo"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?????????</?Menu?>
?????????<?RichTextBox?>
?????????????<?FlowDocument?>
?????????????????<?Paragraph?/>
?????????????</?FlowDocument?>
?????????</?RichTextBox?>
?????</?DockPanel?>
?</?Window?>
Demo中菜單欄的菜單項不僅僅能完美地完成任務(wù)而且能根據(jù)文本框的狀態(tài)和剪切板自動的啟用與禁用,而我們卻沒有為這些復(fù)雜的邏輯編寫任何的后臺代碼。這就是WPF中的命令機制為我們提供了方便。
注意這一行代碼:
?<?MenuItem??Command?="ApplicationCommands.Copy"??Header?="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"?/>
?
我們將“復(fù)制”命令(ApplicationCommands.Copy)賦值給了菜單項的Command屬性,實現(xiàn)了ICommandSource接口的元素都擁有該屬性,這表示該元素可以作為一個“命令源”來引發(fā)某個命令,其Command屬性就指示了其將引發(fā)的命令。
其實一個命令系統(tǒng)是被分為四個部分的:
Command(命令):一個語義級別的輸入,比如“復(fù)制”,“左對齊”,“播放”等
CommandSource(命令源):引發(fā)某命令的元素,比如按鈕,菜單項,鍵盤(Ctrl-C,F1等),鼠標(biāo)等。
CommandTarget(命令目標(biāo)):命令被作用的目標(biāo),比如文本框,播放器等。
CommandBinding(命令綁定):用于將命令和命令的處理邏輯鏈接起來,比如同樣的"粘貼",但粘貼文本和粘貼圖片的處理邏輯是不一樣的,命令綁定負(fù)責(zé)將“粘貼”命令與合理的處理邏輯連接起來。
關(guān)于命令系統(tǒng)將在本文章的后續(xù)部分中講解,不過值得一提的是,在上面的Demo中我們只指定了命令和命令源,并未指定命令目標(biāo),但它會以獲取鍵盤焦點的元素(這里是我們的RichTextBox)作為默認(rèn)值,而命令綁定以及命令的后臺執(zhí)行邏輯被隱藏到了RichTextBox內(nèi)部,那些編寫RichTextBox控件的開發(fā)人員會為我們編寫該部分代碼。
另外,你可能已經(jīng)發(fā)現(xiàn),在Demo中我們并沒有為菜單項標(biāo)題直接設(shè)置“復(fù)制”“粘貼”這樣的文本,而是使用了如下的一個綁定:
?Header="{Binding?Path=Command.Text,?RelativeSource={RelativeSource?Self}}"/>
我們將菜單文本綁定到了命令的Text屬性,這是因為,如果一個命令為RoutedUICommand類型,那么該命令將有一個Text屬性來說明該命令對應(yīng)到的文本名稱,該Text屬性會自動本地化的,也就是說如果你的計算機使用語言是簡體中文的話該菜單項顯示的是“復(fù)制”,如果你的計算機使用的語言是英語的話該菜單項顯示的將是“Copy”。
WPF為我們提供了大量內(nèi)置命令,包括ApplicationCommands,NavigationCommands,,MediaCommands,EditingCommands與ComponentCommands,以及控件開發(fā)人員為它們的控件也提供了很多特有的命令(比如Slider.DecreaseLarge 與 Slider.DecreaseSmall),這些足以應(yīng)付平時的大多數(shù)應(yīng)用,如果還不夠的話,你可以為自己的應(yīng)用自定義更多的命令。
在本隨筆的后續(xù)部分我們將更加深入的探討WPF的命令系統(tǒng),敬請關(guān)注,謝謝。
?
WPF中的命令與命令綁定(二)
在WPF中,命令(Commanding)被分割成了四個部分,分別是ICommand,ICommandSource,CommandTarget和CommandBinding。下面我們來分別探討這四個部分。
1,ICommand
Command也就是我們的“命令”本身,比如“復(fù)制”“粘貼”。在WPF中,所有的命令都必須實現(xiàn)ICommand接口,它為所有的命令提供一個抽象,這個抽象對于我們實現(xiàn)Undo、Redo操作非常重要,如果你學(xué)習(xí)一下設(shè)計模式中的“命令”模式,你會更加深刻的理解。
ICommand接口中擁有Execute()方法,該方法用于命令的執(zhí)行(不過,注意:命令的執(zhí)行邏輯——比如將剪切板中的文本去出來放到文本框的合適位置——并沒有被編寫到該方法中,稍后我們會講到這其中的奧妙),另外的一個方法是CanExecute()用于指示當(dāng)前命令在目標(biāo)元素上是否可用,當(dāng)這種可用性發(fā)生改變時其便會引發(fā)該接口的尾頁一個事件CanExecuteChanged。
在目前的WPF類庫中,你能看到唯一一個實現(xiàn)了ICommand接口的類型RoutedCommand(其實還有一個名為SecureUICommand的類也實現(xiàn)了該接口,不過該類未被公開),“Routed”是一個不太容易被翻譯的修飾詞(有人將它翻譯為“路由”),但這意味著該類型的命令可以向WPF中的RoutedEvent一樣在元素樹中上下傳遞。
RoutedCommand的子類RoutedUICommand是我們經(jīng)常使用的類型,它與RoutedCommand的不同之處僅僅在與它多了一個Text屬性來描述該命令,不過大多數(shù)WPF內(nèi)置命令的Text屬性有一個很不錯的特點:其支持自動本地化。這至少會為我們的軟件的本地化減少工作量。
在本系列隨筆的后續(xù)部分將介紹如何自定義一個命令。
?
2,ICommandSource與CommandTarget
命令源,用來觸發(fā)我們的命令,比如用一個菜單項來觸發(fā)“復(fù)制”命令,那么該菜單項就是命令源。要使一個元素成為命令源,其必須實現(xiàn)ICommandSource接口。命令源決定了它所要觸發(fā)的命令、該命令所作用的對象以及命令參數(shù)(如果需要的話),這分別對應(yīng)于它的三個屬性:Command、CommandTarget以及CommandParameter。其中需要注意的是CommandTarget,因為在WPF中如果你不為命令源指定其命令對象,那么其將會把界面上獲得鍵盤焦點的元素作為默認(rèn)的命令對象,這為我們提供了方便,比如界面上有兩個文本框,我們不必?fù)?dān)心主菜單項上的“粘貼”操作是針對哪個文本框的,誰獲得焦點便針對誰,這符合大家的習(xí)慣。但引入的問題是,如果命令目標(biāo)不具備獲取鍵盤焦點的能力(比如Label)或命令源會搶占焦點(比如用Button來代替菜單項,點擊按鈕時焦點由文本框轉(zhuǎn)移到了按鈕上),你的命令將會無效,這時你就必須為命令源指定命令目標(biāo)。
在本系列隨筆的后續(xù)部分將介紹如何讓你的自定義控件成為命令源和命令目標(biāo)。
3,CommandBinding
前面已經(jīng)提到我們并沒有將命令的執(zhí)行邏輯編寫到其Excute()方法中,這是有道理的,比如"粘貼"命令(ApplicationCommands.Paste),粘貼一段文本到文本框和粘貼一個圖片到繪圖板的執(zhí)行邏輯肯定是不一樣的,負(fù)責(zé)開發(fā)該“粘貼”命令的開發(fā)人員不可能知道所有的粘貼操作的具體邏輯,使用“粘貼”命令的客戶也不應(yīng)該為該執(zhí)行邏輯負(fù)責(zé),編寫該執(zhí)行邏輯的任務(wù)應(yīng)該被分發(fā)給那些支持“粘貼”操作的控件的開發(fā)人員以及那些希望為自己的控件添加“粘貼”操作的客戶。也就是說我們需要將“行為的請求者(命令)”和“行為的執(zhí)行者(命令的執(zhí)行邏輯)”分開而實現(xiàn)一種松耦合,而CommandBinding(命令綁定)便是命令和命令執(zhí)行邏輯的橋接器。
我們使用CommandBinding將命令與其合適的執(zhí)行邏輯綁定在一起:
??CommandBinding?CloseCommandBinding??=???new??CommandBinding(
????ApplicationCommands.Close,?CloseCommandHandler,?CanExecuteHandler);
?
CommandBinding構(gòu)造方法的最后兩個參數(shù)分別是ExecutedRoutedEventHandler?與 CanExecuteRoutedEventHandler 類型的委托,用于指示如何執(zhí)行命令和如何判斷命令能否被執(zhí)行。
與CommandBinding一樣扮演著中間角色的還有CommandManager類,它為命令綁定(以及輸入綁定)提供了很多實用方法。
在本系列隨筆的后續(xù)部分將介紹WPF的命令系統(tǒng)與“命令模式”(設(shè)計模式之一)之間的關(guān)系。
總結(jié)
以上是生活随笔為你收集整理的【转】在WPF中自定义控件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡挂失后找到了可以解挂吗
- 下一篇: 民生白条联名卡可以还京东白条吗