WisdomSoft - for your serial experiences.

5.4 コマンド

複数の UI から起動される共通の処理はコマンドとして抽象化しできます。コントロールがコマンドに対応していれば、イベントハンドラの代わりにコマンドと関連づけることで、複数の UI から共通の処理を呼び出すことができるようになります。

5.4.1 イベント処理の抽象化

一般的な GUI デザインのビジネスアプリケーションであれば、同一の処理を実行するコントロールを重複して配置することがよくあります。例えば、「ファイルを開く」や「コピー」、「ペースト」といった頻繁に使われる項目は、メニュー項目だけではなく、ツールバーやショートカットキーからも起動することができます。メニューバー上の項目と、右クリックで表示するショットカットメニューでも、同じ項目が重複して配置されることがあります。

図1 Microsoft FrontPage のメニュー項目とツールボタン
図1 Microsoft FrontPage のメニュー項目とツールボタン

図1は、Microsoft FrontPage 2003 のメニュー項目とツールバーです。「新規作成」や「開く」などの機能が、メニュー項目とツールバーで重複していることが確認できます。また、「開く」メニュー項目は Ctrl + O キーによるショットカットで実行することも可能です。

これらは、UI 上のコントロールとして分離されているため個別のオブジェクトとして扱わなければなりません。実行する処理内容は同じでも、コントロールが処理するイベントの型が異なるような場合、イベントハンドラを個別に実装する必要がありました。

WPF では、複数の場所から呼び出される論理的なイベントを抽象化したコマンドと呼ばれる機能を利用することで、機能を呼び出す複数のアクションと、機能を提供するイベントハンドラを容易に接続することができます。コマンドとは ICommand インターフェイスを実装するクラスのことを指し、ICommand インターフェイスは System.Windows.Input.RoutedCommand クラスで実装されています。

System.Windows.Input.RoutedCommand クラス
[TypeConverterAttribute("System.Windows.Input.CommandConverter, PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")] 
public class RoutedCommand : ICommand

RoutedCommand クラスのコンストラクタは、オーバーロードされています。コンストラクタから、プロパティに設定するコマンド名などを設定することができます。

RoutedCommand クラスのコンストラクタ
public RoutedCommand ()
public RoutedCommand (string name, Type ownerType)

name パラメータにはコマンド名を表す文字列を、ownerType パラメータにはこのコマンドを所有する型を指定します。通常、コマンドは何らかのクラスの静的なメンバから提供されるため、ここにはコマンドを提供するクラスを指定します。

コンストラクタに指定した値は、プロパティからも設定・取得することができます。コマンド名は Name プロパティ、コマンドを所有する型は OwnerType プロパティを使います。

RoutedCommand クラス Name プロパティ
public string Name { get; }
RoutedCommand クラス OwnerType プロパティ
public Type OwnerType { get; }

コマンドの実行や識別に、これらの値は何ら重要な役割は果たしません。コマンドを実行するイベントハンドらがコマンドを識別したい場合などに、コマンド名を利用することは可能ですが、WPF のコマンド処理は、コマンド名ではなく ICommand オブジェクトそのもので識別します。同一のコマンド名のオブジェクトでも、インスタンスが異なる場合、異なるコマンドとなります。こうした事情から、コマンドはコマンドを提供するクラスの静的なメンバから取得し、同一のコマンドを実行するコントロール間などで共有することになります。

コマンドは、コマンド情報を提供するだけでイベントハンドラを保有しているわけではありません。コマンドとイベントハンドラの接続は System.Windows.Input.CommandBinding クラスを使って行われます。

System.Windows.Input.CommandBinding クラス
public class CommandBinding

このクラスには、以下のようなコンストラクタが用意されています。

CommandBinding クラスのコンストラクタ
public CommandBinding ()
public CommandBinding (ICommand command)

command パラメータには、この CommandBinding オブジェクトと接続されるコマンドを指定します。CommandBinding オブジェクトに設定されているコマンドが実行されると、CommandBinding オブジェクトのイベントハンドラが実行される仕組みになります。

コンストラクタで指定した command パラメータは、Command プロパティから設定・取得することも可能です。

CommandBinding クラス Command プロパティ
[LocalizabilityAttribute(LocalizationCategory.NeverLocalize)] 
public ICommand Command { get; set; }

CommandBinding クラスには、コマンドが発行された時に実行する Executed イベントが用意されています。このイベントは、CommandBinding オブジェクトが保有しているコマンドが実行されたときに呼び出されます。

CommandBinding クラス Executed イベント
public event ExecutedRoutedEventHandler Executed

Executed イベントには System.Windows.Input.ExecutedRoutedEventHandler デリゲートを登録します。

System.Windows.Input.ExecutedRoutedEventHandler デリゲート
public delegate void ExecutedRoutedEventHandler (
	Object sender, ExecutedRoutedEventArgs e
)

sender には、このイベントを発生させたオブジェクトが、e にはイベント情報を格納する System.Windows.Input.ExecutedRoutedEventArgs クラスのオブジェクトが渡されます。

System.Windows.Input.ExecutedRoutedEventArgs クラス
System.Object 
   System.EventArgs 
     System.Windows.RoutedEventArgs 
      System.Windows.Input.ExecutedRoutedEventArgs
public sealed class ExecutedRoutedEventArgs : RoutedEventArgs

ExecutedRoutedEventArgs は、実行されたコマンドの情報をプロパティから提供します。コマンドが実行されたときに指定されたコマンドのパラメータを Parameter プロパティから、コマンドそのものを Command プロパティから取得できます。

ExecutedRoutedEventArgs クラス Parameter プロパティ
public Object Parameter { get; }
ExecutedRoutedEventArgs クラス Command プロパティ
public ICommand Command { get; }

Executed イベントに追加したイベントハンドラは、渡された ExecutedRoutedEventArgs オブジェクトのプロパティでコマンドの種類を把握することができます。

コマンドを実行するには RoutedCommand クラスの Execute() メソッドを呼び出します。この Execute() メソッドを実行し、このコマンドを処理することができる CommandBinding が発見されたとき、対象の CommandBinding の Execute イベントが実行されます。

RoutedCommand クラス Execute() メソッド
public void Execute (Object parameter, IInputElement target)

parameter パラメータには、ExecutedRoutedEventHandler デリゲートの e パラメータに渡される ExecutedRoutedEventArgs オブジェクトの Parameter プロパティに入る、コマンド任意のオブジェクトを指定することができます。target には IInputElement インターフェイスを実装する対象オブジェクトを設定するとありますが、ここに指定できるのは UIElement または ContentElement 型のいずれかです。

ここまでのクラスとプロパティの関係を見ると、CommandBinding が一方的に ICommand オブジェクトを保有するだけであり、コマンドを実行する ICommand からは、コマンドのイベントハンドラを提供する CommandBinding オブジェクトに関連付けられいません。コマンドを発行した時、コマンドを処理するイベントハンドラは Execute() メソッドの target に指定された要素から検索される仕組みになっています。

UIElement または ContentElement には、任意の数の CommandBinding オブジェクトを登録することができる CommandBindings プロパティが提供されています。 

UIElement クラス CommandBindings プロパティ
public CommandBindingCollection CommandBindings { get; }

CommandBindings プロパティは、CommandBinding オブジェクトの配列を管理する CommandBindingCollection コレクションオブジェクトを返します。要素がコマンドを処理する場合、対象の UIElement オブジェクトの CommandBindings プロパティに、処理対象のコマンドに関連付けられた CommandBinding オブジェクトを追加します。

Execute() メソッドによってコマンドが発行されると、target に指定した要素からルートにさかのぼって、要素の CommandBindings に登録されている CommandBinding オブジェクトが検索されます。そのため、対象の要素に処理可能なハンドラが見つからなくても、その親要素が検索されます。例えば、メニュー項目や、ツールバーなどの異なるコントロールからコマンドを実行し、それらを所有する親要素でコマンドを処理することができます。この考え方は、イベントルーティングで解説したものと同じです。

コード1
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

class Test : Window {
	[STAThread]
	public static void Main() {
		Window wnd = new Test();
		Application app = new Application();
		app.Run(wnd);
	}

	private TextBlock textBlock;
	public Test() {
		textBlock = new TextBlock();
		textBlock.FontSize = 30;

		RoutedCommand command = new RoutedCommand("Loaded", typeof(Test));

		CommandBinding cmdBinding = new CommandBinding(command);
		cmdBinding.Executed += executed;

		CommandBindings.Add(cmdBinding);
		Content = textBlock;

		command.Execute("・・・---・・・", this);
	}

	public void executed(Object sender, ExecutedRoutedEventArgs e) {
		string name = ((RoutedCommand)e.Command).Name;
		textBlock.Text = "Command=" + name + "\nParameter=" + e.Parameter;
	}
}
実行結果
コード1 実行結果

コード1は、コンストラクタの最後で生成した RoutedCommand オブジェクトの Execute() メソッドを呼び出してコマンドを発行しています。コマンドの対象には自分自身を選択しているため、自分自身に設定されている CommandBinding オブジェクトから、発行されたコマンドに一致するハンドラを検索します。実行結果を見れば、適切に CommandBinding オブジェクトに設定した executed() メソッドが実行されていることを確認できます。

5.4.2 コマンドの実行許可

状況によっては、コマンドが実行できないことも珍しくはありません。例えば、「編集」関連のコマンドでは、選択範囲が設定されていない状態で「コピー」や「切り取り」といったコマンドは実行できないでしょう。そこで、CommandBinding クラスでは、コマンドが実行可能可能かを検出する CanExecute イベントが用意されています。

CommandBinding クラス CanExecute イベント
public event CanExecuteRoutedEventHandler CanExecute

このイベントには、System.Windows.Input.CanExecuteRoutedEventHandler デリゲートを追加することができます。

System.Windows.Input.CanExecuteRoutedEventHandler デリゲート
public delegate void CanExecuteRoutedEventHandler (
	Object sender, CanExecuteRoutedEventArgs e
)

CanExecuteRoutedEventHandler の e パラメータには、CommandBinding オブジェクトがコマンドを実行可能な状態であるかどうかを設定する System.Windows.Input.CanExecuteRoutedEventArgs クラスのオブジェクトが渡されます。

System.Windows.Input.CanExecuteRoutedEventArgs クラス
System.Object 
   System.EventArgs 
     System.Windows.RoutedEventArgs 
      System.Windows.Input.CanExecuteRoutedEventArgs
public sealed class CanExecuteRoutedEventArgs : RoutedEventArgs

CanExecuteRoutedEventArgs オブジェクトは、コマンドが実行可能かどうかを調べる CanExecute プロパティを保有します。このプロパティは set 悪切磋も提供しているため、CanExecute イベントのハンドラでコマンドが実行可能かどうかを調べ、必要に応じてコマンドの実行を拒否することができます。

CanExecuteRoutedEventArgs クラス CanExecute プロパティ
public bool CanExecute { get; set; }

CanExecute プロパティが true の場合はイベントが実行できることを表します。既定では false が設定されているので、CanExecute イベントを処理するときはイベントハンドラでオブジェクトの状態を調べ、実行可能な状態であれば CanExecute に true を、そうでなければ false を設定します。

また、CanExecuteRoutedEventArgs クラスも、コマンドを取得する Command プロパティや、パラメータを取得する Parameter プロパティを提供しています。

CanExecuteRoutedEventArgs クラス Parameter プロパティ
public Object Parameter { get; }
CanExecuteRoutedEventArgs クラス Command プロパティ
public ICommand Command { get; }

指定したコマンドが実行可能かどうかを調べるには CanExecute() メソッドを使います。

RoutedCommand クラス CanExecute() メソッド
public bool CanExecute (Object parameter, IInputElement target)

このメソッドのパラメータは Execute() メソッドと同じです。例えば、コマンドを実行するコントロールが画面上に表示されたときに、設定されているコマンドが実行可能かどうかを CanExecute() メソッドで調べ、実行ができない状態であればコントロールを無効にするなどの使い方ができます。実際に、コマンドを用いる WPF のコントロールは、CanExecute() メソッドを呼び出して実行できるかどうかを常に調べています。

コード2
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

class Test : Window {
	[STAThread]
	public static void Main() {
		Window wnd = new Test();
		Application app = new Application();
		app.Run(wnd);
	}

	private TextBlock textBlock;
	public Test() {
		textBlock = new TextBlock();
		textBlock.FontSize = 30;

		RoutedCommand command = new RoutedCommand("Loaded", typeof(Test));

		CommandBinding cmdBinding = new CommandBinding(command);
		cmdBinding.CanExecute += canExecute;

		CommandBindings.Add(cmdBinding);
		Content = textBlock;

		bool result = command.CanExecute("情報封鎖が甘い", this);
		textBlock.Text += "\nResult=" + result;
	}

	public void canExecute(Object sender, CanExecuteRoutedEventArgs e) {
		string name = ((RoutedCommand)e.Command).Name;
		textBlock.Text = "Command=" + name + "\nParameter=" + e.Parameter;
		e.CanExecute = true;
	}
}
実行結果
コード2 実行結果

コード2は、作成したコマンドの CanExecute() を呼び出して、CommandBinding の CanExecute イベントが実行されていることを証明するプログラムです。処理の流れや構造はコード1と同じであることを確認してください。

5.4.3 用意されたコマンド

アプリケーション独自の意味を持つコマンドは、作成するプログラム内で用意しなければなりませんが、多くのアプリケーションで共通する基本的な意味のコマンドは、System.Windows.Input.ApplicationCommands クラスで提供されています。

System.Windows.Input.ApplicationCommands クラス
public static class ApplicationCommands

前述したように、コマンドはコマンド名ではなくインスタンスで識別されます。ApplicationCommands クラスは、静的な読み取り専用プロパティからコマンドを返してくれるため、複数のコントロールなどが ApplicationCommands のコマンドを共有することができます。

ApplicationCommands クラスは、アプリケーションが提供する一般的な処理をコマンドとして提供しています。ApplicationCommands は多くのコマンドを提供しているため、そのすべてを紹介することはできません。詳細はドキュメントなどを参照してください。例えば「閉じる」というコマンドは Close プロパティから取得することができます。

ApplicationCommands クラス Close プロパティ
public static RoutedUICommand Close { get; }

Close プロパティが返す RoutedUICommand 型は RoutedCommand クラスの直接の派生クラスです。コマンドの説明を行うテキスト情報を保有している以外は RoutedCommand と同じなので、詳細は割愛させていただきます。

6.7 メニュー コード7」で ApplicationCommands クラスが提供する Close コマンドを使っています。このクラスを用いた複数のコントロール間のコマンドの共有については「6.7 メニュー コード7」を参考にしてください。