命令概觀
命令是 Windows Presentation Foundation (WPF) 中的輸入機制,比起裝置輸入,可提供語意程度更高的輸入處理。 命令的範例有 [複製]、[剪下] 和 [貼上] 等等,在許多應用程式中都可以找到。
本概觀會說明 WPF 中有哪些命令、哪些類別屬於命令模型的一部分,以及如何在應用程式中使用和建立命令。
此主題包括下列章節:
什麼是命令
WPF 中的簡單命令範例
WPF 命令中的四個主要概念
命令程式庫
建立自訂命令
什麼是命令
命令有數種用途。 第一個用途是區隔語意與從執行命令的邏輯叫用該命令的物件。 這可讓多個不同的來源叫用相同的命令邏輯,並可根據不同的目標 (Target) 自訂命令邏輯。 例如,[複製]、[剪下] 和 [貼上] 編輯作業都可以在許多應用程式中找到,但如果它們是使用命令進行實作,則會使用不同的使用者介面來叫用。 應用程式可能會讓使用者按一下按鈕、選擇功能表中的項目或使用按鍵組合 (例如 CTRL+X),來剪下選取的物件或文字。 使用命令,可以將每種使用者動作類型繫結至相同的邏輯。
命令的另一個用途是指出動作是否可用。 繼續以剪下物件或文字為例,動作只有在選取某個項目時才會有意義。 如果使用者嘗試剪下物件或文字,而未選取任何項目,則不會進行任何處理。 為了向使用者指出發生此狀況,許多應用程式都會停用按鈕和功能表項目,讓使用者知道是否可以執行動作。 實作 CanExecute 方法,命令可以指出是否可以執行動作。 按鈕可以訂閱 CanExecuteChanged 事件,並在 CanExecute 傳回 false 時予以停用,而在 CanExecute 傳回 true 時予以啟用。
命令的語意無論在哪一個應用程式或類別都是一致的,但動作的邏輯則會視其作用的特定物件而定。 組合鍵 CTRL+X 會在文字類別、影像類別和 Web 瀏覽器中叫用 [剪下] 命令,但執行 [剪下] 作業的實際邏輯則是由執行剪下的應用程式所定義。 RoutedCommand 可讓用戶端實作邏輯。 文字物件可能會將選取的文字剪下至剪貼簿,影像物件則可能是剪下選取的影像。 應用程式在處理 Executed 事件時可以存取命令的目標,而且可以根據目標的類型採取適當的動作。
WPF 中的簡單命令範例
要在 WPF 中使用命令,最簡單的方式就是從其中一個命令程式庫類別使用預先定義的 RoutedCommand,使用有處理命令原生支援的控制項,以及可叫用命令之原生支援的控制項。 Paste 命令是 ApplicationCommands 類別中的一個預先定義的命令。 TextBox 控制項具有處理 Paste 命令的內建邏輯。 而 MenuItem 類別則有叫用命令的原生支援。
下列範例顯示如何設定 MenuItem,這樣在 TextBox 具有鍵盤焦點的情況下,按下該功能表列時會叫用 TextBox 上的 Paste 命令。
<StackPanel>
<Menu>
<MenuItem Command="ApplicationCommands.Paste" />
</Menu>
<TextBox />
</StackPanel>
' 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
// 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;
WPF 命令中的四個主要概念
WPF 中的路由命令模型可細分為四個主要概念:命令、命令來源、命令目標和命令繫結:
「命令」(Command) 是要執行的動作。
「命令來源」(Command Source) 是叫用命令的物件。
「命令目標」(Command Target) 是執行命令的目標物件。
「命令繫結」(Command Binding) 是將命令邏輯對應到命令的物件。
在上一個範例中,Paste 是命令,MenuItem 是命令來源,TextBox 是命令目標,而命令繫結則是由 TextBox 控制項所提供。 請注意,CommandBinding 不一定都是由命令目標類別的控制項所提供。 CommandBinding 常常必須由應用程式開發人員建立,CommandBinding 也可能被附加到命令目標的祖系。
命令
WPF 中的命令是透過實作 ICommand 介面所建立。 ICommand 會公開兩種方法 (Execute 和 CanExecute) 和一個事件 (CanExecuteChanged)。 Execute 會執行與命令相關聯的動作。CanExecute 會判斷命令是否能在目前的命令目標上執行。 如果集中管理命令操作的命令管理員偵測到命令來源中發生變更,而且此變更可能會使已引發但命令繫結尚未執行的命令無效,則會引發 CanExecuteChanged。 ICommand 的 WPF 實作是 RoutedCommand 類別,也就是本概觀的重點。
WPF 中的主要輸入來源為滑鼠、鍵盤、筆跡和路由命令。 裝置導向的輸入會使用 RoutedEvent,通知應用程式頁中的物件有輸入事件發生。 RoutedCommand 也是如此。 RoutedCommand 的 Execute 和 CanExecute 方法不含命令的應用程式邏輯,但會引發在項目樹狀結構進行向下 (Tunnel) 和反昇 (Bubble) 的路由事件,直到遇到具備 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 時才適用。 如果在 ICommandSource 上設定 CommandTarget,而且對應命令不是 RoutedCommand,就會忽略命令目標。 如果沒有設定 CommandTarget,有鍵盤焦點的項目將會是命令目標。
CommandParameter 是使用者定義資料型別,用來將資訊傳遞給實作命令的處理常式。
實作 ICommandSource 的 WPF 類別為ButtonBase、MenuItem、Hyperlink 和 InputBinding。 按一下 ButtonBase、MenuItem 和 Hyperlink 時,它們會叫用命令,而 InputBinding 會在與其關聯的 InputGesture 執行時叫用命令。
下列範例顯示如何使用 ContextMenu 中的 MenuItem,當做 Properties 命令的命令來源。
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Properties" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
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
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;
通常,命令來源會接聽 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>
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)
Me.InputBindings.Add(OpenCmdKeybinding)
KeyGesture OpenKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);
KeyBinding OpenCmdKeybinding = new KeyBinding(
ApplicationCommands.Open,
OpenKeyGesture);
this.InputBindings.Add(OpenCmdKeybinding);
另外一種將 InputGesture 與 RoutedCommand 產生關聯的方式,是將 InputGesture 加進 RoutedCommand 上的 InputGestureCollection。
下列範例顯示如何將 KeyGesture 加入到 RoutedCommand 的 InputGestureCollection。
Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)
ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)
KeyGesture OpenCmdKeyGesture = 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
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)
Me.CommandBindings.Add(OpenCmdBinding)
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
ApplicationCommands.Open,
OpenCmdExecuted,
OpenCmdCanExecute);
this.CommandBindings.Add(OpenCmdBinding);
接下來,會建立 ExecutedRoutedEventHandler 和 CanExecuteRoutedEventHandler。 ExecutedRoutedEventHandler 會開啟 MessageBox,其中顯示指出命令已執行的字串。 CanExecuteRoutedEventHandler 會將 CanExecute 屬性設定為 true。
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 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 OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = True
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
CommandBinding 會附加到特定物件,例如應用程式的根 Window 或控制項。 CommandBinding 附加的物件會定義繫結的範圍。 例如,附加到命令目標祖系的 CommandBinding 位在 Executed 事件的範圍內,但附加到命令目標子系 (Descendant) 的 CommandBinding 則不在其範圍內。 這是 RoutedEvent 從引發事件的物件以通道的方式向下和反昇向上路由所產生的直接結果。
在某些情況下,CommandBinding 會附加到命令目標本身,例如 TextBox 類別以及 Cut、Copy 和 Paste 命令。 通常,將 CommandBinding 附加到命令目標的祖系會更為方便,例如主要 Window 或 Application 物件,尤其是如果相同的 CommandBinding 可用於多個命令目標。 這些是在建立命令基礎結構時所要考量的設計決定。
命令目標
命令目標是執行命令的項目。 就 RoutedCommand 而言,命令目標是開始路由 Executed 和 CanExecute 的項目。 如前所述,在 WPF 中,ICommandSource 上的 CommandTarget 屬性只有在 ICommand 是 RoutedCommand 時才適用。 如果在 ICommandSource 上設定 CommandTarget,而且對應命令不是 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
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
// 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;
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"。您也可以取得其他輸入裝置的繫結,例如 Tablet PC 畫筆動作和語音資訊。
當您使用 XAML 參考多個命令程式庫中的命令時,通常可以省略公開靜態命令屬性之程式庫類別的類別名稱。 一般來說,命令名稱是明確的字串,而其型別是要提供命令的邏輯分組,但要清楚指定命令不一定要用到它。 例如,您可以指定 Command="Cut",而非指定一長串的 Command="ApplicationCommands.Cut"。 這是 WPF XAML 處理器針對命令內建的便利機制 (精確地說,這是 ICommand 的型別轉換子行為,WPF XAML 處理器會在載入時間 (Load Time) 加以參考)。
建立自訂命令
如果命令程式庫中的命令不敷所需,您可以自行建立命令。 有兩種方式可以建立自訂命令。 第一種方式是從頭開始,並實作 ICommand 介面。 另一種較常用的方式則是建立 RoutedCommand 或 RoutedUICommand。
如需建立自訂 RoutedCommand 的範例,請參閱建立自訂 RoutedCommand 範例 (英文)。