かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF4.5入門 その57「コマンド」

WPFには、ICommandインターフェースというユーザーの操作を抽象化する仕組みがあります。ICommandインターフェースは、以下のように定義されています。

public interface ICommand
{
        //     コマンドを実行するかどうかに影響するような変更があった場合に発生します。
        event EventHandler CanExecuteChanged;

        //     現在の状態でこのコマンドを実行できるかどうかを判断するメソッドを定義します。
        //
        // パラメーター:
        //   parameter:
        //     コマンドで使用されたデータ。 コマンドにデータを渡す必要がない場合は、このオブジェクトを null に設定できます。
        //
        // 戻り値:
        //     このコマンドを実行できる場合は true。それ以外の場合は false。
        bool CanExecute(object parameter);

        //     コマンドの起動時に呼び出されるメソッドを定義します。
        //
        // パラメーター:
        //   parameter:
        //     コマンドで使用されたデータ。 コマンドにデータを渡す必要がない場合は、このオブジェクトを null に設定できます。
        void Execute(object parameter);
}

コマンドが実行可能かどうかという状態に変化があったことを通知するCanExecuteChangedイベントと、実際にコマンドが実行可能かどうかを返すCanExecuteメソッドがあります。そして、コマンドの処理を実行するためのExecuteメソッドがあります。

ICommandは、ICommandSourceという以下のようなインターフェースを実装したクラスに対して設定することが出来ます。ICommandSourceは以下のように定義されています。

//     コマンドを呼び出す方法を認識しているオブジェクトを定義します。
public interface ICommandSource
{
    //     コマンド ソースが呼び出されると実行されるコマンドを取得します。
    //
    // 戻り値:
    //     コマンド ソースが呼び出されると実行されるコマンド。
    ICommand Command { get; }

    //     コマンドの実行時にコマンドに渡すことのできるユーザー定義データの値を表します。
    //
    // 戻り値:
    //     コマンド固有のデータ。
    object CommandParameter { get; }

    //     コマンドが実行されているオブジェクト。
    //
    // 戻り値:
    //     コマンドが実行されているオブジェクト。
    IInputElement CommandTarget { get; }
}

実行するコマンドを取得するためのCommandプロパティと、コマンドに渡すためのCommandParameterが定義されています。CommandTargetは後述するRoutedCommandのみに適用される特殊なプロパティなのでここでは省略します。ICommandSourceインターフェースは、ButtonBase(ボタン系コントロールの基本クラス)やMenuItemなど、ユーザーがアクションを実行するコントロールに主に実装されています。

WPFでのICommandインターフェースの実装クラスのRoutedCommandクラスは、CommandBindingという仕組みを通じてユーザーのアクションと処理を結びつける機能を持っています。RoutedCommandクラスは、以下のように、クラスの静的メンバーとして定義して使用します。

public partial class MainWindow : Window
{
    public static RoutedCommand AlertCommand = new RoutedCommand();

    public MainWindow()
    {
        InitializeComponent();
    }
}

このRoutedCommandと、実際の処理を結びつけるには、UIElementクラスに定義されているCommandBindingsプロパティにCommandBindingを設定して行います。一般的にWindowクラスのCommandBindingsプロパティを使って以下のように定義します。

<Window.CommandBindings>
    <CommandBinding 
        Command="{x:Static local:MainWindow.AlertCommand}" 
        Executed="CommandBinding_Executed"
        CanExecute="CommandBinding_CanExecute"/>
</Window.CommandBindings>

CommandBindingクラスのCommandプロパティに、先程定義したCommandのインスタンスを設定します。そして、ExecuteイベントにCommandの実行時の処理のイベントハンドラと、CanExecuteイベントにCommandの実行可否の処理のイベントハンドラを設定します。ここでは、仮にCheckBoxコントロールが画面にあり、CheckBoxコントロールにチェックがされているときだけ実行可能なコマンドを定義します。画面にCheckBoxコントロールと、コマンドを実行するためのButtonを置きます。ButtonのCommandプロパティには、CommandBindingに設定したものと同じ、MainWindowクラスのAlertCommandプロパティを設定します。

<Window x:Class="CommandSample01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CommandSample01"
        Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding 
            Command="{x:Static local:MainWindow.AlertCommand}" 
            Executed="CommandBinding_Executed"
            CanExecute="CommandBinding_CanExecute"/>
    </Window.CommandBindings>
    <StackPanel>
        <CheckBox x:Name="checkBox" Content="CanExecute"/>
        <Button Content="AlertCommand" Command="{x:Static local:MainWindow.AlertCommand}" />
    </StackPanel>
</Window>

そして、コードビハインドでイベントハンドラの処理を記述します。

public partial class MainWindow : Window
{
    public static RoutedCommand AlertCommand = new RoutedCommand();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("Hello world");
    }

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = this.checkBox.IsChecked.Value;
    }
}

全体のつながりを説明すると、CommandBindingとButtonのCommandに同じコマンドのインスタンスを設定することで、この2つを繋げます。そして実際の処理は、CommandBindingのイベントハンドラで行います。実行結果を以下に示します。

実行すると、以下のようにボタンが押せない状態で起動します。

f:id:okazuki:20141029220803p:plain

CheckBoxコントロールにチェックを入れると、ボタンが押せるようになります。これはCommandBindingに設定したCanExecuteイベントのイベントハンドラで行っている処理でCheckBoxコントロールのチェック状態を見てイベント引数のCanExecuteプロパティに実行可否の値を設定しているためです。

f:id:okazuki:20141029220835p:plain

そして、Buttonコントロールをクリックすると、CommandBindingのExecuteイベントのイベントハンドラが実行されてMessageBoxが表示されます。

f:id:okazuki:20141029220902p:plain

このように、RoutedCommandクラスと、CommandBindingクラスを使うことで操作を表すコマンドと実際の処理を分離して記述することが出来ます。

コマンドは、InputBindingを使うことで簡単にキーボードショートカットやマウスジェスチャーに対応させることが出来ます。WindowなどのInputBindingsプロパティに、KeyBindingを設定することでキーボードショートカットとコマンドの関連付けを行うことが出来ます。KeyBindingのModifiersプロパティに修飾キーを設定して、Keyプロパティにキーを設定して、Commandプロパティに該当するキーボードが押されたときに実行する処理を表すコマンドを設定します。例として、先程のAlertCommandをCtrl+Alt+Aを押したときに表示するようにするコードを示します。

<Window.InputBindings>
    <KeyBinding 
        Modifiers="Alt+Control" 
        Key="A"
        Command="{x:Static local:MainWindow.AlertCommand}" />
</Window.InputBindings>

実行して、CheckBoxコントロールにチェックを入れた状態でCtrl+Alt+Aを押すとMessageBoxが表示されます。

これまでの例では、自分でRoutedCommandのインスタンスを用意したものを使用しましたが、WPFでは組み込みで、アプリケーションによくあるコマンドがあらかじめ定義されています。コピーやペーストなどの一般的な操作をRoutedCommandで定義する場合は、下記のApplicationCommandsクラスに定義されているものを使用するとよいでしょう。

このように、WPFでは、組み込みのICommandインターフェースの実装が提供されています。しかし、ICommandインターフェースを実装していれば、InputBindingなどの機能は使うことが出来ます。最近のWPFをはじめとするXAMLを使った開発ではICommandインターフェースを実装してExecuteやCanExecuteの処理をデリゲートで受け取るDelegateCommand(RelayCommandという名前の場合もある)という実装が使われるのが一般的です。これらのコマンドの独自実装については後述します。

過去記事