命令概觀

命令是 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 公開兩個方法:ExecuteCanExecute,以及一個事件 CanExecuteChangedExecute 執行與命令建立關聯的動作。 CanExecute 判斷命令是否可以在目前命令目標上執行。 如果集中命令作業的命令管理員偵測到命令來源的變更可能會使命令繫結已引發但尚未執行的命令失效,則會引發 CanExecuteChanged。 的 WPF 實 ICommand 作是 類別, RoutedCommand 而且是這個概觀的重點。

WPF 中輸入的主要來源是滑鼠、鍵盤、筆跡和路由命令。 更多裝置導向的輸入使用 RoutedEvent 通知應用程式頁面中的物件有輸入事件發生。 RoutedCommand 並無不同。 RoutedCommandExecuteCanExecute 方法不包含該命令的應用程式邏輯,而是引發路由事件,該事件會在項目樹狀結構中浮動,直到遇到具有 CommandBinding 的物件。 CommandBinding 包含這些事件的處理常式,並且是執行命令的處理常式。 如需 WPF 中事件路由的詳細資訊,請參閱 路由事件概觀

RoutedCommand 上之 Execute 方法會引發命令目標上的 PreviewExecutedExecuted 事件。 RoutedCommand 上之 CanExecute 方法會引發命令目標上的 CanExecutePreviewCanExecute 事件。 這些事件會在項目樹狀結構中浮動,直到遇到有該特定命令之 CommandBinding 的物件。

WPF 提供一組分散在數個類別的一組常見路由命令: MediaCommandsApplicationCommandsNavigationCommandsComponentCommandsEditingCommands 。 這些類別只包含 RoutedCommand 物件,不包含命令的實作邏輯。 實作邏輯是於其上執行命令之物件的責任。

命令來源

命令來源是叫用命令的物件。 命令來源的範例包括 MenuItemButtonKeyGesture

WPF 中的命令來源通常會實 ICommandSource 作 介面。

ICommandSource 會公開三個屬性:CommandCommandTargetCommandParameter

ICommandSource 作的 WPF 類別為 ButtonBaseMenuItemHyperlinkInputBinding 。 按一下 ButtonBaseMenuItemHyperlink 時會叫用一個命令,並且在與其建立關聯的 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 中的兩種輸入手勢是 KeyGestureMouseGesture 。 您可以將 KeyGesture 視為鍵盤快速鍵,例如 CTRL+C。 KeyGestureKey 和一組 ModifierKeys 組成。 MouseGestureMouseAction 和選擇性的一組 ModifierKeys 組成。

為了讓 InputGesture 表現如同命令來源,必須將其與命令建立關聯。 有幾種方法可達成這個目標。 其中一種方式是使用 InputBinding

下列範例會示範如何在 KeyGestureRoutedCommand 之間建立 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)

InputGestureRoutedCommand 建立關聯的另一種方法是將 InputGesture 新增至 RoutedCommand 上的 InputGestureCollection

下列範例示範如何將 KeyGesture 新增至 RoutedCommandInputGestureCollection

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 屬性,以及 PreviewExecutedExecutedPreviewCanExecuteCanExecute 事件。

Command 是與 CommandBinding 建立關聯的命令。 附加到 PreviewExecutedExecuted 事件的事件處理常式會實作命令邏輯。 附加到 PreviewCanExecuteCanExecute 事件的事件處理常式會判斷命令是否可以在目前命令目標上執行。

下列範例將示範如何在應用程式的根目錄 Window 上建立 CommandBindingCommandBinding 會將 Open 命令與 ExecutedCanExecute 處理常式建立關聯。

<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)

接下來,會建立 ExecutedRoutedEventHandlerCanExecuteRoutedEventHandlerExecutedRoutedEventHandler 會開啟 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 會附加至特定的物件,例如應用程式或控制項的根目錄 WindowCommandBinding 所附加至的物件會定義繫結範圍。 例如,附加至命令目標上階的 CommandBinding 可以透過 Executed 事件到達,但無法到達附加至命令目標子系的 CommandBinding。 這是 RoutedEvent 在引發事件之物件中浮動的直接結果。

在某些情況下,CommandBinding 會附加至命令目標本身,例如 TextBox 類別與 CutCopyPaste 命令。 但通常將 CommandBinding 附加至命令目標的上階會更方便,例如主要的 Window 或應用程式物件,特別是當相同的 CommandBinding 可用於多個命令目標時。 這些都是當您要建立命令基礎結構時會想要考慮的設計決策。

命令目標

命令目標是於其上執行命令的項目。 關於 RoutedCommand,命令目標是 ExecutedCanExecute 之路由啟動的項目。 如先前所述,在 WPF 中,上的 CommandTarget 屬性 ICommandSource 僅適用于 是 RoutedCommandICommand 。 如果 CommandTarget 是設定於 ICommandSource 上,並且對應的命令不是 RoutedCommand,則會忽略命令目標。

命令來源可以明確設定命令目標。 如未定義命令目標,則具有鍵盤焦點的項目會作為命令目標。 將具有鍵盤焦點的項目作為命令目標的優點之一是,它讓應用程式開發人員使用相同的命令來源對多個目標叫用命令,但不必追蹤命令目標。 例如,如果 MenuItem 在具有 TextBox 控制項和 PasswordBox 控制項的應用程式中叫用貼上命令,則目標可以是 TextBoxPasswordBox,取決於哪個控制項具有鍵盤焦點。

下例示範如何在標記和程式碼後置中明確設定命令目標。

<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 提供許多與命令相關的功能。 會提供一組靜態方法,用於向特定項目新增和移除 PreviewExecutedExecutedPreviewCanExecuteCanExecute 事件處理常式。 會提供將 CommandBindingInputBinding 物件註冊到特定類別的方法。 CommandManager 也會透過 RequerySuggested 事件提供一種方法,用於在引發 CanExecuteChanged 事件時通知命令。

InvalidateRequerySuggested 方法會強制 CommandManager 引發 RequerySuggested 事件。 出現應該停用/啟用命令的條件,卻非 CommandManager 所知的條件時,這很有用。

命令程式庫

WPF 提供一組預先定義的命令。 命令程式庫包含下列類別:ApplicationCommandsNavigationCommandsMediaCommandsEditingCommandsComponentCommands。 這些類別提供的命令,例如 CutBrowseBack 以及 BrowseForwardPlayStopPause

這些命令許多都包含一組預設的輸入繫結。 例如,如果您指定應用程式處理複製命令,則會自動取得鍵盤系結 「CTRL+C」 您也可以取得其他輸入裝置的系結,例如平板電腦手寫筆手勢和語音資訊。

當您使用 XAML 參考各種命令程式庫中的命令時,通常可以省略公開靜態命令屬性之程式庫類別的類別名稱。 命令名稱通常像字串一樣明確,且擁有提供命令邏輯群組的類型,但不一定用於去除混淆。 例如,您可以指定 Command="Cut" 而不是更詳細的 Command="ApplicationCommands.Cut"。 這是一種便利機制,內建至命令的 WPF XAML 處理器(更確切地說,它是 的型別轉換器行為,WPF XAML 處理器會在載入時參考該行為 ICommand )。

建立自訂命令

如果命令程式庫類別中的命令不符合您的需求,您可以建立自己的命令。 有兩種方式可以建立自訂的命令。 第一種是從頭開始實作 ICommand 介面。 另一種為更常見的方式,即建立 RoutedCommandRoutedUICommand

如需建立自訂 RoutedCommand 的範例,請參閱建立自訂的 RoutedCommand 範例

另請參閱