命令概觀
命令是處理比裝置輸入更接近語意層級的 Windows Presentation Foundation (WPF) 中的輸入機制。 命令範例包含許多應用程式中都有的 [複製]、[剪下] 和 [貼上] 作業。
本概觀會定義 WPF 中有哪些命令、哪些類別屬於命令模型,以及如何在應用程式中使用及建立命令。
本主題包含下列幾節:
命令是什麼
命令有多種用途。 第一個用途是要分隔語意和從執行命令的邏輯叫用命令的物件。 這可讓多個不同的來源叫用相同的命令邏輯,並可針對不同的目標自訂命令邏輯。 例如,許多應用程式中都有的 [複製]、[剪下] 和 [貼上] 等編輯作業;如果使用命令實作,即可使用不同的使用者動作叫用。 應用程式可能會讓使用者按一下按鈕、選擇功能表中的項目,或使用 CTRL+X 等按鍵組合,剪下選取的物件或文字。 您可以使用命令將每一種使用者動作類型繫結至相同的邏輯。
命令的另一項用途是指出是否有動作可用。 若要繼續剪下物件或文字的範例,只有選取項目時動作才有意義。 如果使用者嘗試剪下物件或文字,但未選取任何項目,不會有任何事發生。 為向使用者指出這個狀況,許多應用程式會停用按鈕和功能表項目,讓使用者知道是否能執行某動作。 命令可以透過實作 CanExecute 方法來指出一個動作是否可能執行。 按鈕可以訂閱 CanExecuteChanged 事件,如果 CanExecute 傳回 false
則會停用,如果 CanExecute 傳回 true
則會啟用。
命令語意在不同的應用程式和類別中可以一致,但動作邏輯專屬於於其上執行動作的特定物件。 按鍵組合 CTRL+X 在文字類別、映像類別及網頁瀏覽器中叫用 [剪下] 命令,但執行 [剪下] 作業的實際邏輯是由執行剪下動作的應用程式所定義。 RoutedCommand 可讓用戶端實作邏輯。 文字物件可能會將選取的文字剪入剪貼簿,而映像物件可能會剪下選取的映像。 當應用程式處理 Executed 事件時,可以存取命令目標,並根據目標類型採取適當的動作。
WPF 中的簡單命令範例
在 WPF 中使用命令最簡單的方式,是從命令程式庫類別之一使用預先定義的 RoutedCommand、使用有可處理命令之原生支援的控制項,以及使用有可叫用命令之原生支援的控制項。 Paste 命令是在 ApplicationCommands 類別中的其中一個預先定義命令。 TextBox 控制項具有內建邏輯來處理 Paste 命令。 MenuItem 類別具有叫用命令的原生支援。
下列範例示範如何設定 MenuItem,以便其在按一下時叫用 TextBox 上的 Paste 命令,假設 TextBox 具有鍵盤焦點。
<StackPanel>
<Menu>
<MenuItem Command="ApplicationCommands.Paste" />
</Menu>
<TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();
// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);
// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;
// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()
' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)
' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste
WPF 命令的四個主要概念
WPF 中的路由命令模型可以分成四個主要概念︰命令、命令來源、命令目標和命令繫結:
「命令」是要執行的動作。
「命令來源」是叫用命令的物件。
「命令目標」是在其上執行命令的物件。
「命令繫結」是對應命令邏輯和命令的物件。
在上述範例中,Paste 命令是命令,MenuItem 是命令來源,TextBox 是命令目標,而命令繫結由 TextBox 控制項提供。 值得注意的是,CommandBinding 不一定都是由命令目標類別控制項所提供。 通常,CommandBinding 必須由應用程式開發人員建立,或 CommandBinding 可能會附加至命令目標的上階。
命令
WPF 中的命令是藉由實作 ICommand 介面來建立。 ICommand 公開兩個方法:Execute 和 CanExecute,以及一個事件 CanExecuteChanged。 Execute 執行與命令建立關聯的動作。 CanExecute 判斷命令是否可以在目前命令目標上執行。 如果集中命令作業的命令管理員偵測到命令來源的變更可能會使命令繫結已引發但尚未執行的命令失效,則會引發 CanExecuteChanged。 WPF 的實作是 ICommand 和 RoutedCommand 類別和此概觀焦點所在。
WPF 中的輸入主要來源是滑鼠、鍵盤、筆跡和路由的命令。 更多裝置導向的輸入使用 RoutedEvent 通知應用程式頁面中的物件有輸入事件發生。 RoutedCommand 並無不同。 RoutedCommand 的 Execute 和 CanExecute 方法不包含該命令的應用程式邏輯,而是引發路由事件,該事件會在項目樹狀結構中浮動,直到遇到具有 CommandBinding 的物件。 CommandBinding 包含這些事件的處理常式,並且是執行命令的處理常式。 如需事件在 WPF 中路由的詳細資訊,請參閱路由事件概觀。
RoutedCommand 上之 Execute 方法會引發命令目標上的 PreviewExecuted 和 Executed 事件。 RoutedCommand 上之 CanExecute 方法會引發命令目標上的 CanExecute 和 PreviewCanExecute 事件。 這些事件會在項目樹狀結構中浮動,直到遇到有該特定命令之 CommandBinding 的物件。
WPF 提供分散到數個類別中的一組常見路由命令:MediaCommands、ApplicationCommands、NavigationCommands、ComponentCommands 和 EditingCommands。 這些類別只包含 RoutedCommand 物件,不包含命令的實作邏輯。 實作邏輯是於其上執行命令之物件的責任。
命令來源
命令來源是叫用命令的物件。 命令來源的範例包括 MenuItem、Button 和 KeyGesture。
WPF 中的命令來源通常會實作 ICommandSource 介面。
ICommandSource 會公開三個屬性:Command、CommandTarget 和 CommandParameter:
Command 是當叫用命令來源時,會執行的命令。
CommandTarget 是用來執行命令的物件。 值得注意的是,在 WPF 中,ICommandSource 上的 CommandTarget 屬性僅適用於當 ICommand 為 RoutedCommand 時。 如果 CommandTarget 是設定於 ICommandSource 上,並且對應的命令不是 RoutedCommand,則會忽略命令目標。 如果未設定 CommandTarget,則具有鍵盤焦點的項目就是命令目標。
CommandParameter 是使用者定義資料類型,用來將資訊傳遞至實作命令的處理常式。
實作 WPF 的 ICommandSource 類別是 ButtonBase、MenuItem、Hyperlink 和 InputBinding。 按一下 ButtonBase、MenuItem 和 Hyperlink 時會叫用一個命令,並且在與其建立關聯的 InputGesture 執行時,InputBinding 會叫用一個命令。
下列範例示範如何將 ContextMenu 中的 MenuItem 用作 Properties 命令的命令來源。
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Properties" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
StackPanel cmdSourcePanel = new StackPanel();
ContextMenu cmdSourceContextMenu = new ContextMenu();
MenuItem cmdSourceMenuItem = new MenuItem();
// Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu;
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem);
// Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties;
Dim cmdSourcePanel As New StackPanel()
Dim cmdSourceContextMenu As New ContextMenu()
Dim cmdSourceMenuItem As New MenuItem()
' Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem)
' Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties
一般而言,命令來源會接聽 CanExecuteChanged 事件。 此事件會通知命令來源,命令在目前的命令目標上執行的能力可能已變更。 命令來源可以使用 CanExecute 方法來查詢 RoutedCommand 的目前狀態。 如果命令無法執行,則命令來源可以自行停用。 此情況範例就是 MenuItem 在命令無法執行時把自己變成灰色。
InputGesture 可用作命令來源。 WPF 中兩種類型的輸入筆勢為 KeyGesture 和 MouseGesture。 您可以將 KeyGesture 視為鍵盤快速鍵,例如 CTRL+C。 KeyGesture 由 Key 和一組 ModifierKeys 組成。 MouseGesture 由 MouseAction 和選擇性的一組 ModifierKeys 組成。
為了讓 InputGesture 表現如同命令來源,必須將其與命令建立關聯。 有幾種方法可達成這個目標。 其中一種方式是使用 InputBinding。
下列範例會示範如何在 KeyGesture 和 RoutedCommand 之間建立 KeyBinding。
<Window.InputBindings>
<KeyBinding Key="B"
Modifiers="Control"
Command="ApplicationCommands.Open" />
</Window.InputBindings>
KeyGesture OpenKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);
KeyBinding OpenCmdKeybinding = new KeyBinding(
ApplicationCommands.Open,
OpenKeyGesture);
this.InputBindings.Add(OpenCmdKeybinding);
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)
Me.InputBindings.Add(OpenCmdKeybinding)
將 InputGesture 與 RoutedCommand 建立關聯的另一種方法是將 InputGesture 新增至 RoutedCommand 上的 InputGestureCollection。
下列範例示範如何將 KeyGesture 新增至 RoutedCommand 的 InputGestureCollection。
KeyGesture OpenCmdKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);
ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);
Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)
CommandBinding
CommandBinding 會將命令與實作此命令的事件處理常式建立關聯。
CommandBinding 類別包含 Command 屬性,以及 PreviewExecuted、Executed、PreviewCanExecute 和 CanExecute 事件。
Command 是與 CommandBinding 建立關聯的命令。 附加到 PreviewExecuted 和 Executed 事件的事件處理常式會實作命令邏輯。 附加到 PreviewCanExecute 和 CanExecute 事件的事件處理常式會判斷命令是否可以在目前命令目標上執行。
下列範例將示範如何在應用程式的根目錄 Window 上建立 CommandBinding。 CommandBinding 會將 Open 命令與 Executed 和 CanExecute 處理常式建立關聯。
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open"
Executed="OpenCmdExecuted"
CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
ApplicationCommands.Open,
OpenCmdExecuted,
OpenCmdCanExecute);
this.CommandBindings.Add(OpenCmdBinding);
' Creating CommandBinding and attaching an Executed and CanExecute handler
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)
Me.CommandBindings.Add(OpenCmdBinding)
接下來,會建立 ExecutedRoutedEventHandler 和 CanExecuteRoutedEventHandler。 ExecutedRoutedEventHandler 會開啟 MessageBox,會顯示一個字串,指出該命令已執行。 CanExecuteRoutedEventHandler 會將 CanExecute 屬性設定為 true
。
void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
String command, targetobj;
command = ((RoutedCommand)e.Command).Name;
targetobj = ((FrameworkElement)target).Name;
MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj);
}
Private Sub OpenCmdExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
Dim command, targetobj As String
command = CType(e.Command, RoutedCommand).Name
targetobj = CType(sender, FrameworkElement).Name
MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj)
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = True
End Sub
CommandBinding 會附加至特定的物件,例如應用程式或控制項的根目錄 Window。 CommandBinding 所附加至的物件會定義繫結範圍。 例如,附加至命令目標上階的 CommandBinding 可以透過 Executed 事件到達,但無法到達附加至命令目標子系的 CommandBinding。 這是 RoutedEvent 在引發事件之物件中浮動的直接結果。
在某些情況下,CommandBinding 會附加至命令目標本身,例如 TextBox 類別與 Cut、Copy 和 Paste 命令。 但通常將 CommandBinding 附加至命令目標的上階會更方便,例如主要的 Window 或應用程式物件,特別是當相同的 CommandBinding 可用於多個命令目標時。 這些都是當您要建立命令基礎結構時會想要考慮的設計決策。
命令目標
命令目標是於其上執行命令的項目。 關於 RoutedCommand,命令目標是 Executed 和 CanExecute 之路由啟動的項目。 如前所述,在 WPF 中,ICommandSource 上的 CommandTarget 屬性僅適用於當 ICommand 為 RoutedCommand 時。 如果 CommandTarget 是設定於 ICommandSource 上,並且對應的命令不是 RoutedCommand,則會忽略命令目標。
命令來源可以明確設定命令目標。 如未定義命令目標,則具有鍵盤焦點的項目會作為命令目標。 將具有鍵盤焦點的項目作為命令目標的優點之一是,它讓應用程式開發人員使用相同的命令來源對多個目標叫用命令,但不必追蹤命令目標。 例如,如果 MenuItem 在具有 TextBox 控制項和 PasswordBox 控制項的應用程式中叫用貼上命令,則目標可以是 TextBox 或 PasswordBox,取決於哪個控制項具有鍵盤焦點。
下例示範如何在標記和程式碼後置中明確設定命令目標。
<StackPanel>
<Menu>
<MenuItem Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=mainTextBox}" />
</Menu>
<TextBox Name="mainTextBox"/>
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();
// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);
// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;
// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()
' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)
' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste
The CommandManager
CommandManager 提供許多與命令相關的功能。 會提供一組靜態方法,用於向特定項目新增和移除 PreviewExecuted、Executed、PreviewCanExecute 和 CanExecute 事件處理常式。 會提供將 CommandBinding 和 InputBinding 物件註冊到特定類別的方法。 CommandManager 也會透過 RequerySuggested 事件提供一種方法,用於在引發 CanExecuteChanged 事件時通知命令。
InvalidateRequerySuggested 方法會強制 CommandManager 引發 RequerySuggested 事件。 出現應該停用/啟用命令的條件,卻非 CommandManager 所知的條件時,這很有用。
命令程式庫
WPF 提供一組預先定義的命令。 命令程式庫包含下列類別:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 和 ComponentCommands。 這些類別提供的命令,例如 Cut、BrowseBack 以及 BrowseForward、Play、Stop 和 Pause。
這些命令許多都包含一組預設的輸入繫結。 例如,如果指定應用程式處理複製命令,您會自動取得鍵盤繫結 "CTRL+C"。您也會取得其他輸入裝置的繫結,例如平板電腦筆勢和語音資訊。
當您參考使用 XAML 的各種命令程式庫中的命令時,通常會省略公開靜態命令屬性的文件庫類別的類別名稱。 命令名稱通常像字串一樣明確,且擁有提供命令邏輯群組的類型,但不一定用於去除混淆。 例如,您可以指定 Command="Cut"
而不是更詳細的 Command="ApplicationCommands.Cut"
。 這是內建在命令 WPF XAML 處理器的方便機制 (更精確地說,這是 WPF XAML 處理器在載入階段參考的 ICommand 類型轉換器行為)。
建立自訂命令
如果命令程式庫類別中的命令不符合您的需求,您可以建立自己的命令。 有兩種方式可以建立自訂的命令。 第一種是從頭開始實作 ICommand 介面。 另一種為更常見的方式,即建立 RoutedCommand 或 RoutedUICommand。
如需建立自訂 RoutedCommand 的範例,請參閱建立自訂的 RoutedCommand 範例。