1. 程式人生 > >在WPF中自定義控件

在WPF中自定義控件

pad action sync 這一 www 執行 平時 locks 並不是

一, 不一定需要自定義控件
在使用WPF以前,動輒使用自定義控件幾乎成了慣性思維,比如需要一個帶圖片的按鈕,但在WPF中此類任務卻不需要如此大費周章,因為控件可以嵌套使用以及可以為控件外觀打造一套新的樣式就可以了.是否需要我們來自定義控件,這需要你考慮目前已有控件的真正邏輯功能而不要局限於外觀,如果目前的控件都不能直覺地表達你的想法,那麽你可以自己來打造一個控件,否則,也許我們僅僅改變一下目前控件的模板等就可以完成任務.很多人在自定義控件上經常犯的錯誤是:重復撰寫已有的邏輯

二,UserControl還是CustomControl?
要在WPF中自定義一個控件,使用UserControl與CustomControl都是不錯的選擇(除此之外,還有更多選擇,比如打造一個自定義的面板,但這不在本文的討論範圍),他們的區別在於:
UserControl,其更像WinForm中自定義控件的開發風格,在開發上更簡單快速,幾乎可以簡單地理解為:利用設計器來將多個已有控件作為子元素來拼湊成一個UserControl並修改其外觀,然後後臺邏輯代碼直接訪問這些子元素.其最大的弊端在於:其對模板樣式等支持度不好,其重復使用的範圍有限.
CustomControl, 其開發出來的控件才真正具有WPF風格,其對模板樣式有著很好的支持,這是因為打造CustomControl時做到了邏輯代碼與外觀相分離,即使換上一套完全不同的視覺樹其同樣能很好的工作,就像WPF內置的控件一樣.
在使用Visual Studio打造控件時,UserControl與CustomControl的差別就更加明顯,在項目中添加一個UserControl時,我們會發現設計器為我們添加了一個XAML文件以及一個對應的.CS文件(或.VB等),然後你就可以像設計普通窗體一樣設計該UserControl; 如果我們是在項目中添加一個CustomControl,情況卻不是這樣,設計器會為我們生成一個.CS文件(或.VB等),該文件用於編寫控件的後臺邏輯,而控件的外觀卻定義在了軟件的應用主題(Theme)中了(如果你沒有為軟件定義通用主題,其會自動生成一個通用主題themes\generic.xaml, 然後主題中會自動為你的控件生成一個Style),並將通用主題與該控件關聯了起來.這也就是CustomControl對樣式的支持度比UserControl好的原因.

三,繼承於UserContorl,Control還是其它?
如果你準備打造一個控件,並使用像Visual Studio這樣的工具來開發的話,打造UserControl時其會自動為你從System.Windows.Controls.UserControl繼承,打造CustomControl時其會為從System.Windows.Controls.Control繼承.但實際情況下,也許我們從他們的衍生類別開始繼承會得到更多的好處(更好的重用已有的邏輯),比如你的控件擁有更多的類似於Button的某些特性,那麽從Button開始繼承就比從Control繼承少寫很多代碼.

在接下來的幾節中,我們會逐步討論如何打造UserControl與CustomControl以及讓它們更好支持WPF新特性.

在這裏我們將將打造一個UserControl(用戶控件)來逐步講解如何在WPF中自定義控件,並將WPF的一些新特性引入到自定義控件中來.
我們制作了一個帶語音報時功能的鐘表控件, 效果如下:
技術分享圖片

在VS中右鍵單擊你的項目,點擊"添加新項目",在出現的選擇列表中選擇"UserControl",VS會自動為你生成一個*.xaml文件以及其對應的後臺代碼文件(*.cs或其它).
值得註意的是,自動生成的代碼中,你的控件是繼承於System.Windows.Controls.UserControl類的,這對應你的控件而言並不一定是最恰當的基類,你可以修改它,但註意你應該同時修改*.cs文件和*.xaml文件中的基類,而不只是修改*.cs文件,否則當生成項目時會報錯"不是繼承於同一基類".修改*.xaml文件的方法是:將該文件的第一行和最後一行的"UserControl"改成與你認為恰當的基類名稱.

1,為控件添加屬性(依賴屬性,DependencyProperty)
正如下面的代碼所示:

技術分享圖片public static readonly DependencyProperty TimeProperty =
技術分享圖片 DependencyProperty.Register("Time", typeof(DateTime), typeof(ClockUserCtrl),
技術分享圖片 new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)));

我們為控件(或者任何一個WPF類)添加的依賴屬性都是"公開的","靜態的","只讀的",其命名方式是"屬性名+Property",這是依賴屬性一成不變的書寫方式.對於依賴屬性的註冊可以在聲明該屬性時就調用DependencyProperty.Register()方法註冊,也可以在其靜態構造方法中註冊.上面的DependencyProperty.Register方法的幾個參數分別是:屬性名(該屬性名與聲明的依賴屬性名稱"XXXProperty"相比僅僅是少了"Property"後綴,其它完全一樣,否則在運行時會報異常),屬性的數據類型,屬性的擁有者的類型,元數據.
關於參數中傳遞的元數據:如果是普通的類則應該傳遞PropertyMetadata,如果是FrameworkElement則可以傳遞FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中可以制定一些標記表明該屬性發生變化時控件應該做出什麽反應,比如某屬性的變化會影響到該控件的繪制,那麽就應該像這樣書寫該屬性的元數據: new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);這樣當該屬性發生變化時系統會考慮重繪該控件.另外元數據中還保護很多內容,比如默認值,數據驗證,數據變化時的回調函數,是否參與屬性"繼承"等.
然後,我們將該依賴屬性包裝成普通屬性:

技術分享圖片 [Description("獲取或設置當前日期和時間")]
技術分享圖片 [Category("Common Properties")]
技術分享圖片 public DateTime Time
技術分享圖片 {
技術分享圖片 get
技術分享圖片 {
技術分享圖片 return (DateTime)this.GetValue(TimeProperty);
技術分享圖片 }
技術分享圖片 set
技術分享圖片 {
技術分享圖片 this.SetValue(TimeProperty, value);
技術分享圖片 }
技術分享圖片 }

GetValue和SetValue方法來自於DependencyObject類,其用於獲取或設置類的某屬性值.
註意:在將依賴屬性包裝成普通屬性時,在get和set塊中除了按部就班的調用GetValue和SetValue方法外,不要進行任何其它的操作.下面的代碼是不恰當的:

技術分享圖片 [Description("獲取或設置當前日期和時間")]
技術分享圖片 [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方法來獲取或設置屬性值,那為什麽我們不直接使用該方法來獲取或設置屬性值,而要將其包裝成普通的.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)),我們為屬性的變化指定了一個回調函數,當該屬性變化時該回調函數就會被執行:

技術分享圖片 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()對應的幾個參數分別為:事件名稱,事件傳閱的方式(向上傳閱,向下傳閱或不傳閱),事件對應的EventHandler的類型,事件擁有者的類型)
然後將事件包裝成普通的.NET事件:

技術分享圖片 [Description("日期或時間被更新後發生")]
技術分享圖片 public event RoutedPropertyChangedEventHandler<DateTime> TimeUpdated
技術分享圖片 {
技術分享圖片 add
技術分享圖片 {
技術分享圖片 this.AddHandler(TimeUpdatedEvent, value);
技術分享圖片 }
技術分享圖片 remove
技術分享圖片 {
技術分享圖片 this.RemoveHandler(TimeUpdatedEvent, value);
技術分享圖片 }
技術分享圖片 }

註意,與依賴屬性一樣,不要在add與remove塊中添加除AddHandler與RemoveHandler以外的代碼.
題外話,事件參數中的e.Handled=true並不是終止事件的傳閱,這只是為事件做一個標記而已,以便在默認情況下的讓那些事件處理函數在該標記為true的情況下不被調用,要為該標記為true的事件註冊處理方法並讓該方法得到執行,請使用AddHandler方法,並把最後一個參數handlerEventsToo設置為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內置控件一樣的命令是一件很不錯的事情(事實上這也是在CustomControl中降低界面和後臺邏輯耦合度的一種方法,本系列隨筆中的下一篇中將會具體談談).
WPF中內置的命令有兩大類型:RoutedCommand以及RoutedUICommand,後者比前者多了一個Text屬性用於在界面上自動本地化地顯示該命令對應的文本,更多的可以參考WPF中的命令與命令綁定(一)以及WPF中的命令與命令綁定(二).
這裏我們來定義一個命令,其功能是控件的語音報時.首先我們定義一個命令:

技術分享圖片 public static readonly RoutedUICommand SpeakCommand = new RoutedUICommand("Speak", "Speak", typeof(ClockUserCtrl));
技術分享圖片

參數分別為命名的顯示名稱,命令的名稱,命令的擁有者類型.
然後在控件的靜態函數中定義一個命令綁定,該命令綁定定義了命令的具體細節:對應的命令是什麽?其完成什麽樣的功能,當前環境下其能執行嗎?

技術分享圖片 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屬性用於指示當前命令是否可用,也就是說系統會不斷地檢視該命令與該命令的作用對象,並根據你所提供的條件來判斷當前命令是否可用,比如文本框狀態變為"只讀"後,其"粘貼"命令將不可用,作用於該文本框的粘貼按鈕會自動被禁用,反之則啟用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委托指定了當該命令被執行時所要完成的任務,這通過回調ExcuteSpeak函數來實現.

技術分享圖片 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 = "現在時刻," +
技術分享圖片 localTime.ToShortDateString() +","+
技術分享圖片 localTime.ToShortTimeString() +
技術分享圖片 ",星期" + (int)localTime.DayOfWeek;
技術分享圖片
技術分享圖片 this.speecher.SpeakAsync(textToSpeak);
技術分享圖片 }

我們也可以為命令添加快捷鍵,這是通過InputBinding來實現的,其將命令與命令的快捷鍵關聯起來,比如:

技術分享圖片 InputBinding inputBinding = new InputBinding(SpeakCommand, new MouseGesture(MouseAction.LeftClick));
技術分享圖片 CommandManager.RegisterClassInputBinding(typeof(ClockUserCtrl), inputBinding);

這樣,當我們鼠標點擊控件時就會引發控件的Speak命令,從而調用SpeakTheTime函數進行語音播報.
快捷鍵可以通過MouseGesture或KeyGesture來定義.

4,優點與缺點:
正如在在WPF中自定義控件(1) 中談到的一樣,UserControl能比較快速的打造自定義控件,但其對模板樣式等缺乏很好的支持,打造出來的控件不如WPF內置控件一樣靈活,在本系列隨筆的下一篇中,我們將介紹如何打造能對WPF新特性提供完全支持的CustomControl.

DEMO

WPF中的命令與命令綁定(一)

說到用戶輸入,可能我們更多地會聯想到鍵盤、鼠標、手寫筆,其實還用一種高級別的輸入——命令(Commands),從WPF類庫角度講他們分別對於Keyboard,Mouse,Ink與ICommand。命令是一種語義級別的輸入而不是設備級別的,比如“復制”與“粘貼”,但實現一個命令可以有很多中方式,比如“粘貼”,我們可以使用CTRL-V,也可以使用主菜單或右鍵菜單(上下文菜單)等等。在以往的.net版本中,要在軟件界面上添加一個“粘貼”按鈕,是非常麻煩的事情,你得監視剪切板中是否有可用的文本以及對應的文本框是否獲得了焦點以便啟用或禁用該按鈕,當粘貼時你還得從剪切板中取得相應的文本並插入到文本框的合理位置,等等。

在WPF中提供的命令機制能非常簡單地實現這些任務,下面的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中菜單欄的菜單項不僅僅能完美地完成任務而且能根據文本框的狀態和剪切板自動的啟用與禁用,而我們卻沒有為這些復雜的邏輯編寫任何的後臺代碼。這就是WPF中的命令機制為我們提供了方便。

註意這一行代碼:

技術分享圖片<MenuItem Command="ApplicationCommands.Copy" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>

我們將“復制”命令(ApplicationCommands.Copy)賦值給了菜單項的Command屬性,實現了ICommandSource接口的元素都擁有該屬性,這表示該元素可以作為一個“命令源”來引發某個命令,其Command屬性就指示了其將引發的命令。

其實一個命令系統是被分為四個部分的:
Command(命令):一個語義級別的輸入,比如“復制”,“左對齊”,“播放”等
CommandSource(命令源):引發某命令的元素,比如按鈕,菜單項,鍵盤(Ctrl-C,F1等),鼠標等。
CommandTarget(命令目標):命令被作用的目標,比如文本框,播放器等。
CommandBinding(命令綁定):用於將命令和命令的處理邏輯鏈接起來,比如同樣的"粘貼",但粘貼文本和粘貼圖片的處理邏輯是不一樣的,命令綁定負責將“粘貼”命令與合理的處理邏輯連接起來。
關於命令系統將在本文章的後續部分中講解,不過值得一提的是,在上面的Demo中我們只指定了命令和命令源,並未指定命令目標,但它會以獲取鍵盤焦點的元素(這裏是我們的RichTextBox)作為默認值,而命令綁定以及命令的後臺執行邏輯被隱藏到了RichTextBox內部,那些編寫RichTextBox控件的開發人員會為我們編寫該部分代碼。

另外,你可能已經發現,在Demo中我們並沒有為菜單項標題直接設置“復制”“粘貼”這樣的文本,而是使用了如下的一個綁定:

技術分享圖片Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>

我們將菜單文本綁定到了命令的Text屬性,這是因為,如果一個命令為RoutedUICommand類型,那麽該命令將有一個Text屬性來說明該命令對應到的文本名稱,該Text屬性會自動本地化的,也就是說如果你的計算機使用語言是簡體中文的話該菜單項顯示的是“復制”,如果你的計算機使用的語言是英語的話該菜單項顯示的將是“Copy”。


WPF為我們提供了大量內置命令,包括ApplicationCommands,NavigationCommands,,MediaCommands,EditingCommands與ComponentCommands,以及控件開發人員為它們的控件也提供了很多特有的命令(比如Slider.DecreaseLarge 與 Slider.DecreaseSmall),這些足以應付平時的大多數應用,如果還不夠的話,你可以為自己的應用自定義更多的命令。

在本隨筆的後續部分我們將更加深入的探討WPF的命令系統,敬請關註,謝謝。

WPF中的命令與命令綁定(二)

在WPF中,命令(Commanding)被分割成了四個部分,分別是ICommand,ICommandSource,CommandTarget和CommandBinding。下面我們來分別探討這四個部分。

1,ICommand
Command也就是我們的“命令”本身,比如“復制”“粘貼”。在WPF中,所有的命令都必須實現ICommand接口,它為所有的命令提供一個抽象,這個抽象對於我們實現Undo、Redo操作非常重要,如果你學習一下設計模式中的“命令”模式,你會更加深刻的理解。
ICommand接口中擁有Execute()方法,該方法用於命令的執行(不過,註意:命令的執行邏輯——比如將剪切板中的文本去出來放到文本框的合適位置——並沒有被編寫到該方法中,稍後我們會講到這其中的奧妙),另外的一個方法是CanExecute()用於指示當前命令在目標元素上是否可用,當這種可用性發生改變時其便會引發該接口的尾頁一個事件CanExecuteChanged。
在目前的WPF類庫中,你能看到唯一一個實現了ICommand接口的類型RoutedCommand(其實還有一個名為SecureUICommand的類也實現了該接口,不過該類未被公開),“Routed”是一個不太容易被翻譯的修飾詞(有人將它翻譯為“路由”),但這意味著該類型的命令可以向WPF中的RoutedEvent一樣在元素樹中上下傳遞。
RoutedCommand的子類RoutedUICommand是我們經常使用的類型,它與RoutedCommand的不同之處僅僅在與它多了一個Text屬性來描述該命令,不過大多數WPF內置命令的Text屬性有一個很不錯的特點:其支持自動本地化。這至少會為我們的軟件的本地化減少工作量。

在本系列隨筆的後續部分將介紹如何自定義一個命令。

2,ICommandSource與CommandTarget
命令源,用來觸發我們的命令,比如用一個菜單項來觸發“復制”命令,那麽該菜單項就是命令源。要使一個元素成為命令源,其必須實現ICommandSource接口。命令源決定了它所要觸發的命令、該命令所作用的對象以及命令參數(如果需要的話),這分別對應於它的三個屬性:Command、CommandTarget以及CommandParameter。其中需要註意的是CommandTarget,因為在WPF中如果你不為命令源指定其命令對象,那麽其將會把界面上獲得鍵盤焦點的元素作為默認的命令對象,這為我們提供了方便,比如界面上有兩個文本框,我們不必擔心主菜單項上的“粘貼”操作是針對哪個文本框的,誰獲得焦點便針對誰,這符合大家的習慣。但引入的問題是,如果命令目標不具備獲取鍵盤焦點的能力(比如Label)或命令源會搶占焦點(比如用Button來代替菜單項,點擊按鈕時焦點由文本框轉移到了按鈕上),你的命令將會無效,這時你就必須為命令源指定命令目標。

在本系列隨筆的後續部分將介紹如何讓你的自定義控件成為命令源和命令目標。

3,CommandBinding
前面已經提到我們並沒有將命令的執行邏輯編寫到其Excute()方法中,這是有道理的,比如"粘貼"命令(ApplicationCommands.Paste),粘貼一段文本到文本框和粘貼一個圖片到繪圖板的執行邏輯肯定是不一樣的,負責開發該“粘貼”命令的開發人員不可能知道所有的粘貼操作的具體邏輯,使用“粘貼”命令的客戶也不應該為該執行邏輯負責,編寫該執行邏輯的任務應該被分發給那些支持“粘貼”操作的控件的開發人員以及那些希望為自己的控件添加“粘貼”操作的客戶。也就是說我們需要將“行為的請求者(命令)”和“行為的執行者(命令的執行邏輯)”分開而實現一種松耦合,而CommandBinding(命令綁定)便是命令和命令執行邏輯的橋接器。
我們使用CommandBinding將命令與其合適的執行邏輯綁定在一起:

技術分享圖片 CommandBinding CloseCommandBinding = new CommandBinding(
技術分享圖片 ApplicationCommands.Close, CloseCommandHandler, CanExecuteHandler);

CommandBinding構造方法的最後兩個參數分別是ExecutedRoutedEventHandler 與 CanExecuteRoutedEventHandler 類型的委托,用於指示如何執行命令和如何判斷命令能否被執行。
與CommandBinding一樣扮演著中間角色的還有CommandManager類,它為命令綁定(以及輸入綁定)提供了很多實用方法。

在本系列隨筆的後續部分將介紹WPF的命令系統與“命令模式”(設計模式之一)之間的關系。

在WPF中自定義控件