命令的定義:
命令與事件的區別:命令是具有約束性的。
命令還可以控制接收者"先做校驗,再保存,再關閉"。
命令:WPF的命令,實際上就是實現了ICommand接口的類,平時使用最多的是RoutedCommand類,還可以自定義命令。
如何使用自定義命令:
1、命令源:
即命令的發送者,是實現了TCommandSource接口的類,很多界面元素都實現了這個接口。
2、命令目標:
CommandTarget,即命令發送給誰,將作用在誰的身上,命令目標必須是實現了IInputElement接口的類。
3、命令關聯:
CommandBinding,負責把一些外圍邏輯和命令關聯起來,不如執行之前對命令是否可以執行進行判斷,命令執行之后還有哪些后續工作等。
基本元素之間的關系:
1、創建命令類:即獲得一個實現ICommand接口的類,如果命令與具體業務邏輯無關,則使用WPF類庫中的RoutedCommand類即可,如果想得到與業務邏輯相關的專有命令,則需要創建RoutedCommand的派生類。
2、聲明命令實例:使用命令時需要創建命令類的實例。這里有個技巧,一般情況下程序中某種操作只需要一個命令實例與之對應即可。比如對應“保存”這個操作,你可以拿同一個實例去命令每個組件執行其保存功能,因此程序中的命令會使用單件模式(Singleton Pattern)以減少代碼的復雜度。
3、指定命令的源:即指定由誰來發送這個命令。如果把命令看作炮彈,那么命令源就相當于火炮。同一個命令可以有多個源。比如保存命令,既可以由菜單中的保存項來發送,也可以由工具欄中的保存圖標來發送。需要注意的是,一旦把命令指派給命令源,那么命令源就會受命令的影響,當命令不能被執行的時候作為命令源的控件將處在不可用狀態。看來命令這種炮彈很智能,當不滿足發射條件時還會給用來發射它的火炮上一道保險。避免走火。還需要注意,各種控件發送命令的方法不盡相同,比如 Button 和 MenuItem 是在單擊時發送命令,而 ListBoxItem 單擊時表示被選中所以雙擊時才發送命令。
4、指定命令目標:命令目標并不是命令的屬性而是命令源的屬性,指定命令目標是告訴命令源向哪個組件發送命令,無論這個組件是否擁有焦點它都會收到這個命令。如果沒有為命令源指定命令目標,則 WPF 系統認為當前擁有焦點的對象就是命令目標。這個步驟有點像為火炮指定目標。
5、設置命令關聯:炮兵是不能單獨戰斗的,就像炮兵需要偵查兵在射擊前觀察敵情、判斷發射時機,在射擊后觀測射擊效果、幫助修正一樣,WPF 命令需要 CommandBinding 在執行前來幫助判斷是不是可以執行、在執行后做一些事件來“打掃戰場”。
命令實戰:
<Grid><StackPanel x:Name="stackPanel"><Button Content="Send Command" x:Name="btn_1" Margin="5"/><TextBox x:Name="txt_1" Margin="5" Height="100"/></StackPanel></Grid>
?
/// <summary>/// MainWindow.xaml 的交互邏輯/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();InitialCommand();}//第一個參數 Clear 只是一個對命令的命名,用以區分別的命令//第二個參數 是這個命令歸屬于哪個類private RoutedCommand clearCmd = new RoutedCommand("Clear",typeof(MainWindow));public void InitialCommand(){//把命令賦值給命令源(發送者)并指定快捷鍵this.btn_1.Command = this.clearCmd;//添加快捷鍵啟動命令this.clearCmd.InputGestures.Add(new KeyGesture(Key.C,ModifierKeys.Alt));//指定命令目標this.btn_1.CommandTarget = this.txt_1;//創建命令關聯 CommandBinding 是用來定義命令的執行邏輯(Executed 事件)和能否執行的邏輯(CanExecute 事件)//當一個命令被觸發時(例如通過按鈕點擊或快捷鍵),WPF 會沿著元素的邏輯樹向上查找是否有與該命令相關的 CommandBinding。如果找到,則會觸發對應的事件。CommandBinding cb = new CommandBinding();cb.Command = this.clearCmd;//只關注與clearCmd相關的事件cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);//添加是否可以執行的事件cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);//添加執行的事件//把命令關聯安置在外圍控件上?為什么呢?//如果將 CommandBinding 放在按鈕(btn_1)上,那么只有當按鈕本身直接處理命令時才有效。但如果將命令綁定放在外圍控件(如 stackPanel)上,那么整個 stackPanel 內的任何元素(包括按鈕、快捷鍵等)都可以觸發該命令,并且會找到這個 CommandBinding。這樣可以確保命令的邏輯在整個布局范圍內生效。//代碼中,命令可以通過按鈕點擊觸發,也可以通過快捷鍵(Alt + C)觸發。如果將 CommandBinding 放在按鈕上,那么快捷鍵觸發時可能無法找到對應的綁定邏輯。而將命令綁定放在外圍控件上,無論是按鈕點擊還是快捷鍵,都可以正確找到并執行命令。this.stackPanel.CommandBindings.Add(cb);}//當探測命令是否可以執行時,此方法被調用void cb_CanExecute(object sender,CanExecuteRoutedEventArgs e){if (string.IsNullOrEmpty(this.txt_1.Text)){e.CanExecute = false;}else{e.CanExecute = true;}//避免繼續向上傳遞降低程序性能e.Handled = true;}void cb_Executed(object sender,ExecutedRoutedEventArgs e){this.txt_1.Clear();//避免繼續向上傳遞降低程序性能e.Handled = true;}}
對于上面代碼有幾點需要注意的地方:
第一,使用命令可以避免自己寫代碼判斷 Button 是否可用以及添加快捷鍵。 第二,RoutedCommand 是一個與業務邏輯無關的類,只負責在程序中“跑腿”而并不對命令目標做任何操作,TextBox 并不是由它清空的。那么對 TextBox 的清空操作是誰做的呢?答案是 CommandBinding。因為無論是探測命令是否執行還是命令送達目標,都會激發命令目標發送路由事件,這些路由事件會沿著 UI 元素樹向上傳遞并最終被 CommandBinding 所捕捉。本例中 CommandBinding 被安裝在外圍的 StackPanel 上,CommandBinding “站在高處”起一個偵聽器的作用,而且專門針對 clearCmd 命令捕捉與其相關的路由事件。本例中,當 CommandBinding 捕捉到 CanExecute 事件就會調用 cb_CanExecute 方法(判斷命令執行的條件是否滿足,并反饋給命令供其影響命令源的狀態);當捕捉到的是 Executed 事件(表示命令的 Execute 方法已經執行了,或說命令已經作用在了命令目標上,RoutedCommand 的 Execute 方法不包含業務邏輯,只負責讓命令目標激發 Executed),則調用 cb_Executed 方法。 第三,因為 CanExecute 事件的激發頻率比較高,為了避免降低性能,在處理完后建議把 e.Handled 設為 true。 第四,CommandBinding 一定要設置在命令目標的外圍控件上,不然無法捕捉到 CanExecute 和 Executed 等路由事件。?
WPF 的命令庫
命令具有“一處聲明、處處使用”的特點,比如 Save 命令,在程序的任何地方它都表示要求命令目標保存數據。因此,微軟在 WPF 類庫里準備了一些便捷的命令庫,這些命令庫包括:
ApplicationCommands
ComponentCommands
NavigationCommands
MediaCommands
EditingCommands
?命令參數:
前面提到命令庫里有很多 WPF 預制的命令,如 New、Open、Copy、Cut、Paste 等。這些命令都是 ApplicationCommands 類的靜態屬性,所以它們的實例永遠只有一個,這就引出一個問題:如果界面上有兩個按鈕,一個用來新建 Teacher 的檔案,另一個用來新建 Student 的檔案,都使用 New 命令的話,程序應該如何區別新建的是什么檔案呢? 答案是使用 CommandParameter。命令源一定是實現了 ICommandSource 接口的對象,而ICommandSource 有一個屬性就是CommandParameter,如果把命令看作飛向目標的炮彈,那么 CommandParameter 就相當于裝載在炮彈肚子里的“消息”。下面是程序的實現代碼。
<Window x:Class="CommandParameterSample.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Command Parameter"Height="240" Width="360" Background="LightBlue" WindowStyle="ToolWindow"><Grid Margin="6"><Grid.RowDefinitions><RowDefinition Height="24" /><RowDefinition Height="4" /><RowDefinition Height="24" /><RowDefinition Height="4" /><RowDefinition Height="24" /><RowDefinition Height="4" /><RowDefinition Height="*" /></Grid.RowDefinitions><!--命令和命令參數--><TextBlock Text="Name:" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0" /><TextBox x:Name="nameTextBox" Margin="60, 0,0,0" Grid.Row="0" /><Button Content="New Teacher" Command="New" CommandParameter="Teacher" Grid.Row="2" /><Button Content="New Student" Command="New" CommandParameter="Student" Grid.Row="4" /><ListBox x:Name="listBoxNewItems" Grid.Row="6" /></Grid><!--為窗體添加 CommandBinding--><Window.CommandBindings><CommandBinding Command="New" CanExecute="New_CanExecute" Executed="New_Executed" /></Window.CommandBindings>
</Window>
注意:
代碼有兩個值得注意的地方: 兩個按鈕都使用 New 命令,但分別使用字符串 Teacher 和 Student 作為參數。 這次是使用 XAML 代碼為窗體添加 CommandBinding,CommandBinding 的 CanExecute 和 Executed 事件處理器寫在后臺 C#代碼里。
private void New_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{if (string.IsNullOrEmpty(this.nameTextBox.Text)){e.CanExecute = false;}else{e.CanExecute = true;}
}private void New_Executed(object sender, ExecutedRoutedEventArgs e)
{string name = this.nameTextBox.Text;if(e.Parameter.ToString() == "Teacher"){this.listBoxNewItems.Items.Add(string.Format("New Teacher: {0}, 學而不厭、誨人不倦。", name));}if(e.Parameter.ToString() == "Student"){this.listBoxNewItems.Items.Add(string.Format("New Student: {0}, 好好學習、天天向上。", name));}
}
?
?命令與 Binding 的結合
初試命令,你可能會想到這樣一個問題:控件有很多事件,可以讓我們進行各種各樣不同的操
作,可控件只有一個 Command 屬性,而命令庫中卻有數十種命令,這樣怎么可能使用這個唯一的
Command 屬性來調用那么多種命令呢?答案是:使用 Binding。前面已經說過,Binding 作為一種間接的、不固定的賦值手段,可以讓你有機會選擇在某個條件下為目標賦特定的值(有時候需要借助 Converter)。
例如,如果一個 Button 所關聯命令有可能根據某些條件而改變,我們可以把代碼寫成這樣:
<Button x:Name="dynamicCmdBtn" Command="{Binding Path=ppp, Source=sss}" Content="Command" />
Command
:這是一個附加屬性,它告訴WPF按鈕應該執行一個命令。{Binding ...}
:這是一個綁定表達式,用于將按鈕的Command
屬性與視圖模型中的命令屬性進行連接。Path=ppp
:這里的ppp
是一個占位符,代表視圖模型中命令屬性的名稱。例如,如果你的視圖模型中有一個名為SaveCommand
的命令屬性,那么這里的ppp
應該替換為SaveCommand
。Source=sss
:這里的sss
也是一個占位符,代表數據上下文(DataContext)的名稱。在WPF中,數據上下文是綁定的起點,它通常是包含數據和命令的對象。如果你的視圖模型實例被設置為某個控件(如窗口或頁面)的DataContext
,那么這里的sss
應該替換為該控件的XAML名稱,或者是RelativeSource
綁定,指向包含數據上下文的父級控件。