WPF のコマンド実行システムは、RoutedCommand と RoutedEvent に基づいています。コマンドは、RoutedCommand である必要はなく、単に、後で説明する ICommand だけを実装できますが、この概要の大部分は RoutedCommand モデルを中心に説明しています。
コマンドと、ボタンまたはタイマに割り当てられた単純なイベント ハンドラとの違いは、コマンドはアクションのセマンティクス (意味) と発行元がロジックから切り離されるという点です。これにより、複数のさまざまなソースで同じコマンド ロジックを呼び出せるようになり、コマンド ロジックをさまざまな対象用にカスタマイズできるようになります。
コマンドの例としては、多くのアプリケーションで目にする [コピー]、[切り取り]、[貼り付け] などの編集操作があります。コマンドのセマンティクスはアプリケーションおよびクラスの間で一貫していますが、アクションのロジックは実行される特定のオブジェクトに固有のものです。Ctrl+X キーは、テキスト クラス、イメージ クラス、および Web ブラウザでは [切り取り] コマンドを呼び出しますが、[切り取り] 操作を実行するための実際のロジックは、コマンドを呼び出すソースではなく、切り取りが実行されているオブジェクトまたはアプリケーションによって定義されます。テキスト オブジェクトは選択したテキストをクリップボードに切り取り、イメージ オブジェクトは選択したイメージを切り取ることができますが、同じコマンド ソースである KeyGesture または ToolBar ボタンを両方のクラスのコマンドを呼び出すために使用できます。
WPF のルーティング コマンド モデルは、コマンド、コマンド ソース、コマンドの対象、およびコマンド バインディングの 4 つの主要な概念に分けることができます。
コマンドは、実行されるアクションです。
コマンド ソースは、コマンドを呼び出すオブジェクトです。
コマンドの対象は、コマンドが実行されているオブジェクトです。
コマンド バインディングは、コマンド ロジックをコマンドに割り当てるオブジェクトです。
前の例では、Paste コマンドがコマンド、MenuItem がコマンド ソース、TextBox がコマンドの対象であり、コマンド バインディングは TextBox コントロールによって提供されています。ただし、CommandBinding が、コマンドの対象クラスであるコントロールによって必ず提供されるとは限らないことに注意してください。アプリケーション開発者が CommandBinding を作成する必要がある場合や、CommandBinding をコマンドの対象の親に割り当てる場合も多くあります。
コマンド
コマンド ソース
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);
次に、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 イベントによって到達できますが、コマンドの対象の子孫に割り当てられた CommandBinding には到達できません。これは、イベントを発生させるオブジェクトから RoutedEvent がトンネリングおよびバブリングするというしくみが直接影響するからです。
場合によっては、CommandBinding は、TextBox クラスに対する Cut、Copy、Paste の各コマンドのように、コマンドの対象自体に割り当てられます。ただし、CommandBinding は、特に同じ CommandBinding を複数のコマンドの対象で使用できる場合には、メイン Window やアプリケーション オブジェクトなど、コマンドの対象の先祖に割り当てる方が便利な場合がよくあります。これらは、コマンド実行インフラストラクチャの作成時に検討される設計上の決定事項です。
コマンドの対象
コマンドの対象は、コマンドが実行される要素です。RoutedCommand のコマンドの対象は、Executed および CanExecute のルーティングが開始される要素です。前に述べたように、WPF では、ICommandSource の CommandTarget プロパティは、ICommand が RoutedCommand の場合にのみ適用されます。ICommandSource で CommandTarget が設定されていても、対応するコマンドが RoutedCommand でない場合は、コマンドの対象は無視されます。
コマンド ソースは、コマンドの対象を明示的に設定できます。コマンドの対象が定義されていない場合は、キーボード フォーカスを持つ要素がコマンドの対象として使用されます。キーボード フォーカスを持つ要素をコマンドの対象として使用する利点の 1 つは、アプリケーション開発者が同じコマンド ソースを使用して、コマンドの対象を追跡せずにコマンドを複数の対象で呼び出すことができることです。たとえば、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;
CommandManager
WPF は、一連の定義済みコマンドを提供します。このコマンド ライブラリは、ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands、および ComponentCommands クラスで構成されています。これらのクラスは、Cut、BrowseBack と BrowseForward、Play、Stop、Pause などのコマンドを提供します。
これらのコマンドの多くには、一連の既定の入力バインディングが含まれます。たとえば、アプリケーションでコピー コマンドを処理するように指定した場合、キーボード バインディング "Ctrl+C" が自動的に取得されます。Tablet PC ペン ジェスチャや音声情報など、他の入力デバイスのバインディングも取得されます。
XAML を使用してさまざまなコマンド ライブラリ内のコマンドを参照する場合、通常は、静的コマンド プロパティを公開するライブラリ クラスのクラス名を省略できます。一般に、コマンド名は文字列としてあいまいではなく、所有する型は、コマンドを論理的にグループ化するために存在し、あいまいさを排除するために必要なわけではありません。たとえば、冗長な Command="ApplicationCommands.Cut" ではなく、Command="Cut" と指定できます。これは、コマンドの WPF XAML プロセッサに組み込まれている便利なメカニズムであり (もっと正確に言えば、これは ICommand の型コンバータの動作です)、WPF XAML プロセッサが読み込み時に参照します。