コマンド実行の概要

コマンド実行は Windows Presentation Foundation (WPF) の入力メカニズムであり、デバイス入力よりいっそうセマンティックなレベルでの入力処理を提供します。 コマンドの例は、多くのアプリケーションで見られる コピー切り取り貼り付け などの操作です。

この概要では、WPF でのコマンドとはどういうものか、コマンド実行モデルに含まれるクラス、アプリケーションでコマンドを作成して使用する方法を定義します。

このトピックは、次のセクションで構成されています。

コマンドとは

コマンドには、いくつかの目的があります。 第 1 の目的は、コマンドを呼び出すセマンティクスとオブジェクトを、コマンドを実行するロジックから分離することです。 これにより、複数の異なるソースから同じコマンド ロジックを呼び出すことができ、異なるターゲットに合わせてコマンド ロジックをカスタマイズできます。 たとえば、多くのアプリケーションで使われる編集操作である コピー切り取り貼り付け は、コマンドを使って実装されていれば、異なるユーザー アクションを使って呼び出すことができます。 ユーザーは、ボタンをクリックして、メニューの項目を選択して、または Ctrl + X などのキーの組み合わせを使って、選んだオブジェクトやテキストを切り取ることができます。 コマンドを使うことにより、各種のユーザー アクションを同じロジックにバインドできます。

コマンドのもう 1 つの目的は、アクションを実行できるかどうかを示すことです。 再びオブジェクトやテキストの切り取りを例にすると、アクションは何かを選択したときにのみ意味があります。 何も選ばないでオブジェクトまたはテキストを切り取ろうとすると、何も起こりません。 ユーザーにこのことを示すため、多くのアプリケーションでは、ボタンやメニュー項目を無効にして、アクションを実行できるかどうかをユーザーが認識できるようにしています。 コマンドでは、CanExecute メソッドを実装することで、アクションが実行可能かどうかを示すことができます。 ボタンは、CanExecuteChanged イベントをサブスクライブでき、CanExecutefalse が返された場合は無効に、CanExecutetrue が返された場合は有効にできます。

コマンドのセマンティクスはアプリケーションおよびクラス全体で統一できますが、アクションのロジックは対象となる特定のオブジェクトに固有です。 キーの組み合わせ Ctrl + X キーはテキスト クラス、イメージ クラス、Web ブラウザーの 切り取り コマンドを呼び出しますが、切り取り 操作を実行する実際のロジックは、切り取りを実行するアプリケーションによって定義されています。 RoutedCommand では、クライアントでロジックを実装できます。 テキスト オブジェクトは選択されたテキストを切り取ってクリップボードに移動できるのに対し、イメージ オブジェクトは選択されたイメージを切り取ることができます。 アプリケーションは、Executed イベントを処理するときに、コマンドのターゲットにアクセスし、ターゲットの種類に応じた適切なアクションを実行できます。

WPF での簡単なコマンドの例

WPF でコマンドを使う最も簡単な方法は、いずれかのコマンド ライブラリ クラスの定義済みの RoutedCommand を使用するか、コマンドの処理をネイティブにサポートするコントロールを使用するか、コマンドの呼び出しをネイティブにサポートするコントロールを使用することです。 Paste コマンドは、ApplicationCommands クラスで定義済みのコマンドのいずれかです。 TextBox コントロールには、Paste コマンドを処理するためのロジックが組み込まれています。 MenuItem クラスはコマンドの呼び出しをネイティブにサポートします。

次に示すのは、TextBox にキーボード フォーカスがある場合に、クリックされたときに TextBoxPaste コマンドを呼び出すように MenuItem を設定する方法の例です。

<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 のコマンド実行における 4 つの主要な概念

WPF のルーティング コマンド モデルは、コマンド、コマンド ソース、コマンド ターゲット、コマンド バインディングという 4 つの主要な概念に分けることができます。

  • "コマンド" は、実行するアクションです。

  • "コマンド ソース" は、コマンドを呼び出すオブジェクトです。

  • "コマンド ターゲット" は、コマンドが実行されているオブジェクトです。

  • "コマンド バインディング" は、コマンド ロジックをコマンドにマップするオブジェクトです。

前の例では、Paste コマンドがコマンド、MenuItem がコマンド ソース、TextBox がコマンド ターゲットで、コマンド バインドは TextBox コントロールによって提供されます。 注意しなければならないのは、CommandBinding はコマンド ターゲット クラスであるコントロールによって常に提供されるわけではないということです。 多くの場合は、アプリケーション開発者が CommandBinding を作成する必要があるか、または CommandBinding はコマンド ターゲットの先祖に関連付けられています。

コマンド

WPF のコマンドは、ICommand インターフェイスを実装することで作成されます。 ICommand では、2 つのメソッド (ExecuteCanExecute)、およびイベント (CanExecuteChanged) を公開します。 Execute では、コマンドに関連付けられているアクションを実行します。 CanExecute では、現在のコマンド ターゲットでコマンドを実行できるかどうかを判断します。 コマンド実行操作を集中管理するコマンド マネージャーが、既に発生しているがコマンド バインドによってまだ実行されていないコマンドを無効にする可能性のある変更をコマンド ソースで検出した場合、CanExecuteChanged が発生します。 ICommand での WPF の実装は RoutedCommand クラスであり、この概要の焦点になります。

WPF での主な入力ソースは、マウス、キーボード、インク、およびルーティング コマンドです。 デバイス指向性の高い入力では、RoutedEvent を使って、入力イベントが発生したことをアプリケーション ページのオブジェクトに通知します。 RoutedCommand も同様です。 RoutedCommandExecute メソッドと CanExecute メソッドにはコマンドのアプリケーション ロジックは含まれませんが、これらのメソッドが発生させたルーティング イベントは、CommandBinding を含むオブジェクトに遭遇するまで、要素ツリー内をトンネリングおよびバブリングします。 CommandBinding にはこれらのイベント用のハンドラーが含まれ、そのハンドラーがコマンドを実行します。 WPF でのルーティング イベントについて詳しくは、「ルーティング イベントの概要」をご覧ください。

RoutedCommandExecute メソッドは、コマンド ターゲットで PreviewExecuted イベントと Executed イベントを発生させます。 RoutedCommandCanExecute メソッドは、コマンド ターゲットで CanExecute イベントと PreviewCanExecute イベントを発生させます。 これらのイベントは、その特定のコマンドの CommandBinding を持つオブジェクトに遭遇するまで、要素ツリー内をトンネリングおよびバブリングします。

WPF では、MediaCommandsApplicationCommandsNavigationCommandsComponentCommandsEditingCommands などの複数のクラスにわたる共通のルーティング コマンドのセットが提供されます。 これらのクラスは RoutedCommand オブジェクトのみで構成され、コマンドの実装ロジックは含みません。 実装ロジックは、コマンドが実行されるオブジェクトに存在します。

コマンド ソース

コマンド ソースは、コマンドを呼び出すオブジェクトです。 コマンド ソースの例は、MenuItemButtonKeyGesture などです。

WPF のコマンド ソースでは、通常、ICommandSource インターフェイスを実装します。

ICommandSource では、CommandCommandTargetCommandParameter という 3 つのプロパティを公開します。

  • Command は、コマンド ソースが呼び出されたときに実行されるコマンドです。

  • CommandTarget は、コマンドを実行するオブジェクトです。 WPF では、ICommandSourceCommandTarget プロパティは、ICommandRoutedCommand である場合にのみ適用可能であることに注意してください。 ICommandSourceCommandTarget が設定されていて、対応するコマンドが RoutedCommand ではない場合、コマンド ターゲットは無視されます。 CommandTarget が設定されていない場合は、キーボード フォーカスを持つ要素がコマンド ターゲットになります。

  • CommandParameter は、コマンドを実装しているハンドラーに情報を渡すために使われるユーザー定義データ型です。

ICommandSource を実装する WPF クラスは、ButtonBaseMenuItemHyperlinkInputBinding です。 ButtonBaseMenuItemHyperlink は、クリックされたときにコマンドを呼び出します。InputBinding は、関連付けられている InputGesture が実行されたときにコマンドを呼び出します。

次の例では、Properties コマンドのコマンド ソースとして、ContextMenuMenuItem を使用する方法を示します。

<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 イベントをリッスンします。 このイベントは、現在のコマンド ターゲットでのコマンド実行機能が変化した可能性があることを、コマンド ソースに通知します。 コマンド ソースは、RoutedCommand の現在の状態を、CanExecute メソッドを使って照会できます。 コマンドを実行できない場合、コマンド ソースはそれ自体を無効にできます。 たとえば、コマンドを実行できない場合、MenuItem はそれ自体を灰色で表示します。

InputGesture はコマンド ソースとして使用できます。 WPF の 2 種類の入力ジェスチャは、KeyGestureMouseGesture です。 KeyGesture は、Ctrl + C キーなどのキーボード ショートカットと考えることができます。 KeyGesture は、Key と、ModifierKeys のセットで構成されます。 MouseGesture は、MouseAction と、オプションの ModifierKeys のセットで構成されます。

InputGesture がコマンド ソースとして機能するためには、コマンドと関連付けられる必要があります。 これを実現するにはいくつかの方法があります。 1 つ目は、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 に関連付けるもう 1 つの方法は、RoutedCommandInputGestureCollectionInputGesture を追加するというものです。

RoutedCommandInputGestureCollectionKeyGesture を追加する方法を次の例に示します。

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 が関連付けられているコマンドです。 PreviewExecuted および Executed イベントに関連付けられているイベント ハンドラーは、コマンド ロジックを実装します。 PreviewCanExecute および CanExecute イベントに関連付けられているイベント ハンドラーは、現在のコマンド ターゲットでコマンドを実行できるかどうかを判断します。

アプリケーションのルート WindowCommandBinding を作成する方法の例を以下に示します。 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)

次に、ExecutedRoutedEventHandlerCanExecuteRoutedEventHandler が作成されます。 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 が関連付けられるオブジェクトにより、バインドのスコープが定義されます。 たとえば、Executed イベントは、コマンド ターゲットの先祖に関連付けられた CommandBinding には到達できますが、コマンド ターゲットの子孫に関連付けられた CommandBinding には到達できません。 これは、イベントを発生させたオブジェクトからの RoutedEvent のトンネリングとバブリングの方法の直接的な結果です。

状況によっては、CommandBinding は、TextBox クラスや CutCopyPaste コマンドなど、コマンド ターゲット自体に関連付けられます。 ただし、ほとんどの場合は、メイン Window や Application オブジェクトなど、コマンド ターゲットの先祖に CommandBinding を関連付ける方が便利です。同じ CommandBinding を複数のコマンド ターゲットに使うことができる場合は特にそうです。 これらは、コマンド実行のインフラストラクチャを作成するときに考慮する設計上の決定です。

コマンド ターゲット

コマンド ターゲットは、コマンドが実行される要素です。 RoutedCommand の場合、コマンド ターゲットは、Executed および CanExecute のルーティングが開始される要素です。 前述のとおり、WPF では、ICommandSourceCommandTarget プロパティは、ICommandRoutedCommand である場合にのみ適用されます。 ICommandSourceCommandTarget が設定されていて、対応するコマンドが RoutedCommand ではない場合、コマンド ターゲットは無視されます。

コマンド ソースは、コマンド ターゲットを明示的に設定できます。 コマンド ターゲットが定義されていない場合は、キーボード フォーカスを持つ要素がコマンド ターゲットとして使われます。 キーボード フォーカスを持つ要素をコマンド ターゲットとして使うことの、アプリケーション開発者にとっての利点の 1 つは、コマンド ターゲットを追跡しなくても、同じコマンド ソースを使って、複数のターゲットでコマンドを呼び出すことができることです。 たとえば、MenuItemTextBox コントロールと 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

CommandManager

CommandManager は、コマンドに関連する複数の関数を提供します。 特定の要素の PreviewExecutedExecutedPreviewCanExecuteCanExecute イベント ハンドラーを追加および削除するための、静的メソッドのセットを提供します。 CommandBinding および InputBinding オブジェクトを特定のクラスに登録するための手段を提供します。 また、CommandManager は、CanExecuteChanged イベントを発生させる必要があるタイミングをコマンドに通知する手段を、RequerySuggested イベントによって提供します。

InvalidateRequerySuggested メソッドは、CommandManagerRequerySuggested イベントを強制的に発生させます。 これは、コマンドを無効または有効にする必要がある状況であっても、CommandManager がそれを認識しない場合に便利です。

コマンド ライブラリ

WPF には、定義済みのコマンドのセットが用意されています。 コマンド ライブラリは、ApplicationCommandsNavigationCommandsMediaCommandsEditingCommandsComponentCommands というクラスで構成されています。 これらのクラスでは、CutBrowseBackBrowseForwardPlayStopPause などのコマンドが提供されます。

これらのコマンドの多くには、既定の入力バインディングのセットが含まれます。 たとえば、アプリケーションがコピー コマンドを処理することを指定すると、キーボード バインディング "Ctrl + C" が自動的に有効になります。また、タブレット PC のペン ジェスチャや音声情報などの他の入力デバイスに対するバインディングも有効になります。

XAML を使ってさまざまなコマンド ライブラリのコマンドを参照するときは、通常、静的なコマンド プロパティを公開するライブラリ クラスのクラス名を省略できます。 一般に、コマンド名に文字列としての曖昧さはなく、所有する型は、コマンドの論理グループを提供するために存在しますが、曖昧さの解消のためには必要ありません。 たとえば、詳細な Command="ApplicationCommands.Cut" ではなくCommand="Cut" と指定できます。 これは、コマンドに対する WPF XAML プロセッサに組み込まれた便利なメカニズムです (つまり、これは ICommand の型コンバーターの動作であり、WPF XAML プロセッサは読み込み時にこれを参照します)。

カスタム コマンドの作成

コマンド ライブラリ クラスのコマンドがニーズを満たさない場合は、独自のコマンドを作成できます。 カスタム コマンドを作成するには 2 つの方法があります。 1 つは、一から始めて ICommand インターフェイスを実装する方法です。 もう 1 つのさらに一般的な方法は、RoutedCommand または RoutedUICommand を作成するものです。

カスタムの RoutedCommand を作成する例については、「Create a Custom RoutedCommand Sample」 (カスタム RoutedCommand の作成のサンプル) を参照してください。

関連項目