前面章節已經對命令進行了深刻分析,分析了基類和接口以及WPF提供的命令庫。但還沒有例舉任何使用這些命令的例子。編輯器
如前所述,RoutedUICommand類沒有任何硬編碼的功能,而是隻表達命令,爲觸發命令,須要有命令源(也可以使用代碼)。爲響應命令,須要有命令綁定,命令綁定將執行轉發給廣泛的事件處理程序。函數
1、命令源工具
命令庫中的命令始終可用。觸發他們的最簡單的方法是將它們關聯到實現了ICommandSource接口的控件,其中包括繼承自ButtonBase類的控件(Button和CheckBox等)、單獨的ListBoxItem對象、HyperLink以及MenuItem。this
ICommandSource接口定義了三個屬性,以下表所示。編碼
表 ICommandSource接口的屬性spa
例如,下面的按鈕使用Command屬性鏈接到ApplicationCommands.New命令:設計
<Button Command="ApplicationCommands.New">New</Button>code
WPF的智能程度足夠高,它能查找前面介紹的全部5個命令容器類,這意味着可以使用下面的縮寫的形式:xml
<Button Command="New">New</Button>
然而,因爲沒有指明包含命令的類,這種語法不夠明確、不夠清晰。對象
2、命令綁定
當將命令關聯到命令源時,會看到一些有趣的現象。命令源將會被自動禁用。
例如,若是建立上一節提到的New按鈕,該按鈕的顏色就會變淺而且不能被單擊,就像將IsEnabled屬性設置爲false那樣(以下圖所示)。這是由於按鈕已經查詢了命令的狀態,並且因爲命令尚未與其關聯的綁定,因此按鈕被認爲是禁用的。
爲改變這種狀態,須要爲命令建立綁定以明確如下三件事:
當命令被觸發時執行什麼操做。
如何肯定命令是否可以被執行(這是可選的。若是未提供這一細節,只要提供了關聯的事件處理程序,命令老是可用)。
命令在何處起做用。例如,命令可被限制在單個按鈕中使用,或在整個窗口中使用(這種狀況更常見)。
下面的代碼片斷爲New命令建立綁定。可將這些代碼添加到窗口的構造函數中:
//Create the binding CommandBinding binding=new CommandBinding(ApplicationCommands.New); //Attach the event handler binding.Executed+=NewCommand_Executed; //Register the binding this.CommandBinding.Add(binding);
注意,上面建立的CommandBinding對象唄添加到包含窗口的CommandBindings集合中,這經過事件冒泡進行工做。實際上,當單擊按鈕時,CommandBinding.Executed事件從按鈕冒泡到包含元素。
儘管習慣上爲窗口添加全部綁定,但CommandBindings屬性實際是在UIElement基類中定義的。這意味着任何元素都支持該屬性。例如,若是將命令綁定直接添加到使用它的按鈕中,這個示例仍工做的很好(儘管不能在將該綁定重用與其餘高級元素)。爲獲得最大的靈活性,命令綁定一般被添加到頂級窗口。若是但願在多個窗口中使用相同相同的命令,須要在這些窗口中分別建立命令綁定。
上面的代碼假定在同一個類中已有名爲NewCommand_Executed的事件處理程序,該處理程序已經準備好接收命令。下面是一個示例,該例包含一些顯示命令源的簡單代碼:
private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("New command triggered by " + e.Source.ToString()); }
如今,若是容許應用成功需,按鈕處於啓用狀態。若是單擊按鈕,就會觸發Executed事件,該事件冒泡至窗口,並被上面給出的NewCommand_Executed()事件處理程序程序處理。這是,WPF會告知事件源(按鈕)。經過ExecutedRoutedEventArgs對象還可得到被調用的命令的引用(ExecutedRoutedEventArgs.Command),以及全部同時傳遞的額外數據(ExecutedRoutedEventArgs.Parameter)。在該例中,由於沒有傳遞任何額外的數據,因此參數爲null(若是但願傳遞附加數據,贏設置命令源的CommandParameter屬性;而且若是但願傳遞一些來自另外一個控件的信息,還須要使用數據綁定表達式設置CommandParameter屬性)。
在上面的示例中,使用代碼生成了命令綁定。然而,若是但願精簡代碼隱藏文件,使用XAML以生命方式關聯命令一樣容易。下面是所需的標記:
<Window x:Class="Commands.TestNewCommand" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TestNewCommand" Height="300" Width="300"> <Window.CommandBindings> <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"></CommandBinding> </Window.CommandBindings> <StackPanel> <Button Margin="5" Padding="5" Command="ApplicationCommands.New">New</Button> </StackPanel> </Window>
使用Visual Studio沒有爲定義命令綁定提供任何設計時支持。對鏈接控件和命令的支持也較弱。可以使用Properties窗口設置控件的Command屬性,但須要輸入正確的命令名稱——因爲並未提供包含命令的下拉列表,所以不能方便地從列表中選擇命令。
3、使用多命令源
上面示例中觸發普通事件的方式看起來不那麼直接。然而,當添加使用相同命令的更多控件時,額外命令層的意義就提現出出來了。例如,可添加以下也使用New命令的菜單項:
<Menu > <MenuItem Header="File"> <MenuItem Command="New"></MenuItem> </MenuItem> </Menu>
注意,New命令的這個MenuItem對象沒有設置Header屬性。這是由於MenuItem類足夠智能,若是沒有設置Header屬性,它將從命令中提取文本(Button控件不具備這一特性)。雖然該特性帶帶來的便利看起來不大,但若是計劃使用不一樣的語言本地化應用程序,這一特性就很重要了。在這種狀況下,只須要在一個地方修改文本便可(經過設置命令的Text屬性)。這比在整個窗口中進行跟蹤更容易。
MenuItem類還有一項功能:能自動提取Command.InputBinding集合中的第一個快捷鍵(若是存在的話)。對於ApplicationCommands.New命令對象,這意味着在菜單文本的旁邊會顯示Ctrl+N快捷鍵(以下圖所示)。
4、微調命令文本
既然菜單具備自動提取命令項文本的功能,肯恩改回好奇其餘ICommandSource類是否也具備相似功能,如Button控件。
可使用兩種技術重用命令文本。一種選擇是直接從靜態命令對象中提取文本。XAML可以使用Static標記擴展完成這一任務。下面的示例獲取命令名New,並將它做爲按鈕的文本:
<Button Margin="5" Padding="5" Command="New" Content="{x:Static ApplicationCommands.New}"></Button>
該方法的問題在於,它指示調用命令對象命令對象的ToString()方法。所以,獲得的是命令名,而不是命令的文本(對於哪些名稱中包含多個單詞的命令,使用命令文本更好些,由於命令文本包含空格)。雖然解決這一問題,但須要完成更多工做。這種方法還存在一個問題,一個按鈕將同一個命令使用兩次,可能會無心間從錯誤的命令獲取文本)。
更好的解決方案是使用數據綁定表達式。在此使用的數據綁定有些不尋常,由於他綁定到當前元素嗎,獲取正在使用的Command對象,並提取Text屬性。下面是很是複雜的語法:
<Button Margin="5" Padding="5" Command="New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"></Button>
可經過另外一種更具想象力的方式使用該技術。例如,可以使用一幅小圖像設置按鈕的內容,而在按鈕的工具提示中使用數據綁定表達式顯示命令名:
<Button Margin="5" Padding="5" Command="ApplicationCommands.New" ToolTip="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"> <Image Source="redx.jpg" Stretch="None"></Image> </Button>
按鈕的內容能夠是形狀,也能夠是顯示爲縮略圖的位圖。
顯然,這種方法比直接在標記中放置命令文本更麻煩些。然而,若是準備使用不一樣的語言本地化應用程序,使用這個方法是值得的。當應用程序啓動時,只須要爲全部命令設置命令文本便可(若是在建立了命令綁定後改變命令文本,不會產生任何效果。由於Text屬性不是依賴項屬性,因此沒有自動的更改通知來更新用戶界面)。
5、直接調用命令
並不是只能使用實現了ICommandSource接口的類來觸發但願執行的命令。也能夠用Execute()方法直接調用來自任何事件處理程序的方法。這時須要傳遞參數值(或null引用)和對目標元素的引用:
ApplicationCommands.New.Execute(null,targetElement);
目標元素是WPF開始查找命令綁定的地方。可以使用包含窗口(具備命令綁定)或嵌套的元素(例如,實際引起事件的元素)。
也可在關聯的CommandBinding對象中調用Execute()方法。在這種狀況下,不須要提供目標元素,由於會自動將公開正在使用的CommandBindings集合的元素設置爲目標元素。
this.CommandBindings[0].Command.Execute(null);
這種方法只使用了半個命令模型。雖然也觸發命令,但不能響應命令的狀態變化。若是但願實現該特性,當命令變爲啓用或禁用時,也可能但願處理RoutedCommand.CanExecuteChanged事件進行響應。當引起CanExecuteChanged事件時,須要調用RoutedCommand.CanExecute()方法檢查命令是否處於可用狀態。若是命令不可用。可禁用或改變用戶界面中的部份內容。
6、禁用命令
若是想要建立狀態在啓用和禁用之間變化的命令,你將體會到命令模型的真正優點。例如,分析下圖中顯示的單窗口應用程序,它是有菜單、工具欄以及大文本框構成的簡單文本編輯器。該應用程序能夠打開文件,建立新的(空白)文檔,以及保存所執行的操做。
在該應用程序中,保持New、Open、Save、SaveAs以及Close命令一直可用是很是合理的。但還有一種設計,只有當某些操做使文本相對於原來的文件發生了變化時才啓用Save命令。根據約定,可在代碼中使用簡單的Boolean值來跟蹤這一細節:
private bool isDirty = false;
而後當文本發生變化時設置該標誌:
private void txt_TextChanged(object sender, RoutedEventArgs e) { isDirty = true; }
如今須要從窗口命令綁定傳遞信息,使連接的控件可根據須要進行更新。技巧是處理命令綁定的CanExecute事件。可經過下面的代碼爲該事件關聯事件處理程序:
CommandBinding binding = new CommandBinding(ApplicationCommands.Save); binding.Executed += SaveCommand_Executed; binding.CanExecute += SaveCommand_CanExecute; this.CommandBindings.Add(binding);
或使用聲明方式:
<Window.CommandBindings> <CommandBinding Command="ApplicationCommands.Save" Executed="SaveCommand_Executed" CanExecute="SaveCommand_CanExecute"></CommandBinding> </Window.CommandBindings>
在事件處理程序中,只須要檢查isDirty變量,並相應地設置CanExecuteRoutedEventArgs.CanExecute屬性:
private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = isDirty;
}
若是isDirty的值時false,就禁用命令。若是isDirty的值爲true,就啓用命令(若是沒有設置CanExecute標誌,就會保持最近的值)。
當使用CanExecute事件時,還須要理解一個問題,由WPF負責調用RoutedCommand.CanExecute()方法來觸發事件處理程序,並肯定命令的狀態。當WPF命令管理器探測到某個確信十分重要的變化——例如,當焦點從一個控件移到另外一個控件上時,或執行了某個命令後,WPF命令管理器就會完成該工做。控件還能引起CanExecuteChanged事件以通知WPF從新評估命令——例如,當用戶在文本框中按下一個鍵時會發生該事件。總之,CanExecute事件會被頻繁地觸發,而且不該在該事件的處理程序中使用耗時的代碼。
然而,其餘因素可能影響命令狀態。在當前示例中,爲響應其餘操做,可能會修改isDirty標誌。若是發現命令狀態未在正確的時間被更新,可強制WPF爲全部正在使用的命令調用CanExecute()方法。經過調用靜態方法CommandManager.InvalidateRequerySuggested()完成該工做。而後命令管理器觸發RequerySuggested事件,通知窗口中的命令源(按鈕、菜單項等)。此後命令源會從新查詢它們連接的命令並相應地更新它們的狀態。
7、具備內置命令的控件
一個輸入控件可自行處理命令事件。例如,TextBox類處理Cut、Copy以及Paste命令(還有Undo、Redo命令,以及一些來自EditingCommd類的用於選擇文本以及將光標移到不一樣位置的命令)。
當控件具備本身的硬編碼命令邏輯時,爲使命令工做不須要作其餘任何事情。例如,對於上節示例的簡單編輯器,添加以下工具欄按鈕,就會自動獲取對剪切、複製和粘貼文本的支持:
<ToolBar> <Button Command="Cut">Cut</Button> <Button Command="Copy">Copy</Button> <Button Command="Paste">Paste</Button> </ToolBar>
如今淡季這些按鈕中的任意一個(當文本框具備焦點時),就能夠複製、剪切或從剪貼板粘貼文本。有趣的是,文本框還處理CanExecute事件。若是當前未在文本框中選中任何內容,就會禁用剪切和複製命令。當焦點移到其餘不支持這些命令的控件時,會自動禁用全部這三個命令(除非關聯本身的CanExecute事件處理程序以啓動這些命令)。
該例有一個有趣的細節。Cut、Copy和Paste命令被具備焦點的文本框處理。然而,由工具欄上的按鈕觸發的命令時徹底獨立的元素。在該例中,這個過程之因此可以無縫工做,是由於按鈕被放到工具欄上,ToolBar類提供了一些內置邏輯,可將其子元素的CommandTarget屬性動態設置爲當前具備焦點的控件(從技術角度看,ToolBar控件一直在關注着其父元素,即窗口,並在上下文中查找最近具備焦點的控件,即文本框。ToolBar控件有單獨的焦點範圍(focus scope),而且在其上下文中按鈕是具備焦點的)。
若是在不一樣容器(不是ToolBar或Menu控件)中放置按鈕,就不會得到這些優點。這意味着除非手動設置CommanTarget屬性,不然按鈕不能工做。爲此,必須使用命令目標元素的綁定的表達式。例如,若是文本框被命名爲txtDocument,就應該像下面這樣定義按鈕:
<Button Command="Cut" CommandTarget="{Binding ElementName=txtDocument}">Cut</Button> <Button Command="Copy" CommandTarget="{Binding ElementName=txtDocument}">Copy</Button> <Button Command="Paste" CommandTarget="{Binding ElementName=txtDocument}">Paste</Button>
另外一個較簡單的選擇是使用附加屬性FocusManager.IsFocusScope建立新的焦點範圍。當觸發命令時,該焦點範圍會通知WPF在父元素的焦點範圍內查找元素:
<StackPanel FocusManager.IsFocusScope="True"> <Button Command="Cut">Cut</Button> <Button Command="Copy">Copy</Button> <Button Command="Paste">Paste</Button> </StackPanel>
該方法還有一個附加優勢,即相同的命令可應用於多個控件,不像上個示例那樣對CommandTarget進行硬編碼。此外,Menu和ToolBar控件默認將FocusManager.IsFocusScope屬性設置爲true,但若是但願簡化命令路由行爲,不在父元素上下文中查找具備焦點的元素,也可將該屬性設爲false。
在極少數狀況下,你可能發現控件支持內置命令,而你並不想啓用它。在這種狀況下,能夠採用三種方法禁用命令。
理想狀況下,控件提供用於關閉命令支持的屬性,從而確保控件移除這些特性並連貫地調整自身。例如,TextBox控件提供了IsUndoEnabled屬性,爲阻止Undo特性,可將該屬性設置爲false(若是IsUndoEnabled屬性爲true,Ctrl+Z組合鍵將觸發Undo命令)。
若是這種作法行不通,可爲但願禁用的命令添加新的命令綁定。而後該命令綁定可提供新的CanExecute事件處理程序,並老是響應false。下面舉一個使用該技術刪除文本框Cut特性支持的示例:
CommandBinding commandBinding=new CommandBinding(ApplicationCommands.Cut,null,SuppressCommand); txt.CommandBindings.Add(commandBinding);
並且該事件處理程序設置CanExecute狀態:
private void SupressCommand(object sender,CanExecuteRoutedEventArgs e) { e.CanExecute=false; e.Handled=false; }
注意,上面的代碼設置了Handled標誌以阻止文本框自我執行計算,而文本框可能將CanExecute屬性設置爲true。
該方法並不完美。它可成功地爲文本框禁用Cut快捷鍵(Ctrl+X)和上下文菜單中的Cut命令。然而,仍會在上下文菜單中顯示處理禁用狀態的該選項。
最後一種選擇是,使用InputBinding集合刪除觸發命令的輸入。例如,可以使用帶阿媽禁用觸發TextBox控件中的Copy命令的Ctrl+C組合鍵,以下所示:
KeyBinding keyBinding=new KeyBinding(ApplicationCommands.NotACommand,Key.C,ModifierKeys.Control); txt.InputBinding.Add(keyBinding);
技巧是使用特定的ApplicationCommands.NotACommand值,該命令什麼都不作,它專門用於禁用輸入綁定。
當使用這種方法時,仍啓用Copy命令。可經過本身建立的按鈕觸發該命令(或使用文本框的上下文菜單觸發命令,除非也經過將ContextMenu屬性設置爲null刪除了上下文菜單)。