コマンド実行の概要
更新 : 2007 年 11 月
コマンド実行は、デバイス入力よりも意味を持つレベルで入力を処理する Windows Presentation Foundation (WPF) の入力機構です。コマンドの例としては、多くのアプリケーションで目にする [コピー]、[切り取り]、[貼り付け] などの操作があります。
ここでは、WPF にはどんなコマンドがあるか、コマンド実行モデルに含まれるクラス、およびアプリケーションでコマンドを使用および作成する方法について説明します。
このトピックには次のセクションが含まれています。
- コマンドとは
- WPF の簡単なコマンドの例
- WPF のコマンド実行の 4 つの主要な概念
- コマンド ライブラリ
- カスタム コマンドの作成
- 関連トピック
コマンドとは
WPF のコマンド実行システムは、RoutedCommand と RoutedEvent に基づいています。コマンドは、RoutedCommand である必要はなく、単に、後で説明する ICommand だけを実装できますが、この概要の大部分は RoutedCommand モデルを中心に説明しています。
コマンドと、ボタンまたはタイマに割り当てられた単純なイベント ハンドラとの違いは、コマンドはアクションのセマンティクス (意味) と発行元がロジックから切り離されるという点です。これにより、複数のさまざまなソースで同じコマンド ロジックを呼び出せるようになり、コマンド ロジックをさまざまな対象用にカスタマイズできるようになります。
コマンドの例としては、多くのアプリケーションで目にする [コピー]、[切り取り]、[貼り付け] などの編集操作があります。コマンドのセマンティクスはアプリケーションおよびクラスの間で一貫していますが、アクションのロジックは実行される特定のオブジェクトに固有のものです。Ctrl+X キーは、テキスト クラス、イメージ クラス、および Web ブラウザでは [切り取り] コマンドを呼び出しますが、[切り取り] 操作を実行するための実際のロジックは、コマンドを呼び出すソースではなく、切り取りが実行されているオブジェクトまたはアプリケーションによって定義されます。テキスト オブジェクトは選択したテキストをクリップボードに切り取り、イメージ オブジェクトは選択したイメージを切り取ることができますが、同じコマンド ソースである KeyGesture または ToolBar ボタンを両方のクラスのコマンドを呼び出すために使用できます。
WPF の簡単なコマンドの例
WPF でコマンドを使用する最も簡単な方法は、コマンド ライブラリ クラスのいずれかから定義済みの RoutedCommand を使用すること、コマンドの処理をネイティブにサポートしているコントロールを使用すること、およびコマンドの呼び出しをネイティブにサポートしているコントロールを使用することです。Paste コマンドは、ApplicationCommands クラスの定義済みコマンドの 1 つです。TextBox コントロールには、Paste コマンドを処理するためのロジックが組み込まれており、MenuItem クラスは、コマンドの呼び出しをネイティブにサポートしています。
TextBox にキーボード フォーカスがある場合に、MenuItem がクリックされると TextBox で Paste コマンドを呼び出すように 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;
WPF のコマンド実行の 4 つの主要な概念
WPF のルーティング コマンド モデルは、コマンド、コマンド ソース、コマンドの対象、およびコマンド バインディングの 4 つの主要な概念に分けることができます。
コマンドは、実行されるアクションです。
コマンド ソースは、コマンドを呼び出すオブジェクトです。
コマンドの対象は、コマンドが実行されているオブジェクトです。
コマンド バインディングは、コマンド ロジックをコマンドに割り当てるオブジェクトです。
前の例では、Paste コマンドがコマンド、MenuItem がコマンド ソース、TextBox がコマンドの対象であり、コマンド バインディングは TextBox コントロールによって提供されています。ただし、CommandBinding が、コマンドの対象クラスであるコントロールによって必ず提供されるとは限らないことに注意してください。アプリケーション開発者が CommandBinding を作成する必要がある場合や、CommandBinding をコマンドの対象の親に割り当てる場合も多くあります。
コマンド
WPF のコマンドは、ICommand インターフェイスを実装することによって作成されます。ICommand は、Execute と CanExecute の 2 つのメソッドと 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 の 3 つのプロパティを公開します。
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>
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 の 2 種類があります。KeyGesture は Ctrl + C などのキーボード ショートカットと考えることができます。KeyGesture は、Keyと、ModifierKeys のセットで構成されています。MouseGesture は、MouseAction と、オプションの ModifierKeys のセットで構成されています。
InputGesture をコマンド ソースとして機能させるには、コマンドに関連付ける必要があります。これを実現するには、いくつかの方法があります。1 つは、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);
InputGesture を RoutedCommand に関連付ける別の方法は、InputGesture を RoutedCommand の InputGestureCollection に追加する方法です。
KeyGesture を RoutedCommand の InputGestureCollection に追加する方法を次の例に示します。
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
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
CommandManager は、多数のコマンド関連機能を提供します。PreviewExecuted、Executed、PreviewCanExecute、および CanExecute の各イベント ハンドラを特定の要素に対して追加および削除するための一連の静的メソッドを提供します。CommandBinding オブジェクトと InputBinding オブジェクトを特定のクラスに登録する手段を提供します。また、CommandManager は、RequerySuggested イベントを介して、CanExecuteChanged イベントを発生させるタイミングをコマンドに通知する方法も提供します。
InvalidateRequerySuggested メソッドは、CommandManager に RequerySuggested イベントを強制的に発生させます。これは、コマンドを無効または有効にする必要がある場合に役立ちますが、CommandManager が認識しない場合には使用しません。InvalidateRequerySuggested を呼び出すことによって、CommandManager に RequerySuggested イベントを強制的に発生させる例については、「ディスパッチャのタイマによるコマンド ソースの無効化のサンプル」を参照してください。
コマンド ライブラリ
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 プロセッサが読み込み時に参照します。
カスタム コマンドの作成
コマンド ライブラリ クラス内のコマンドが目的に合っていない場合は、独自のコマンドを作成できます。カスタム コマンドを作成するには 2 とおりの方法があります。1 つは、最初から作成する方法で ICommand インターフェイスを実装します。もう 1 つは、RoutedCommand または RoutedUICommand を作成する方法で、こちらの方が一般的です。
カスタムの RoutedCommand の作成例については、「カスタム RoutedCommand の作成のサンプル」を参照してください。
参照
処理手順
ディスパッチャのタイマによるコマンド ソースの無効化のサンプル