MVVMパターンでは有名な Expression Blend SDK に含まれる System.Windows.Interactivity名前空間に含まれる クラス (EventTrigger, InvokeAction, Interaction 等) を使用してコントロールのイベントとViewModel (今回の例では Window のプロパティ) で公開される ICommand コマンドプロパティとをコードビハインドファイルやハンドラを使用せずに関連付ける方法を記載します。

そもそもなぜ Command と イベントを関連付けるかを記載します。MVVMパターンでは  ICommandSource を実装ている コントロール (Buttonなど) とViewModel等のICommandを実装したクラスのプロパティをxaml 上で関連付けて処理を実現します。ListBox など ICommandSource を実装していないクラスの場合イベントと コマンド用のViewModelのプロパティと関連付ける処理がxaml上で行えません。コードビハインドファイルにイベントハンドラを記載することになります。それとは別に ICommandSource を実装していたとしても、特定のイベント(Buttonの場合は Click) に対応する処理をしたときのみに 関連付けられた Command が実行されるため、MouseEnterなど任意のイベントで特定のコマンドを実行させるという記述がxaml上でできません。

今回記載する方法では、ListBox などの任意のイベントとViewModel等で公開される ICommand 実装クラスのプロパティを関連付けることで イベントハンドラをコードビハインドファイルに記述しなくて済むようになります。

任意のイベントとCommandを関連付ける方法として有名な方法(ほかにもあると思いますが) は、次の2つの方法です。

  1. Attached Property の機能を利用した Attached Behavior  パターンを使用する
  2. Expression Blend SDK に含まれる System.Windows.Interactivity 名前空間のクラスを使用する 

どちらも MVVMパターンに取り組まれている方にはあたりまえの方法かもしれません。今回はより汎用性の高いパターン2 の方法を実装してみたかったのでその覚書を記載します。

環境は次の通り

  • Visual Studio 2012 Ultimate
  • .NET 4.5
  • Microsoft Expression Blend 4 Software Development Kit (SDK) for .NET 4 (バージョン:2.0.20621.0)

1. Expression Blend SDK のダウンロードとインストール

.NET 4.0 用で 最新の Expression Blend SDK をダウンロードします。記事記載段階での最新のURLを下記に記載します。

Microsoft Expression Blend 4 Software Development Kit (SDK) for .NET 4
http://www.microsoft.com/ja-jp/download/details.aspx?id=10801

ダウンロードした MSIファイルをダブルクリックして ウィザードに従ってインストールを行ってください。

2. サンプルプログラムの作成

Visual Studio を起動して WPF プロジェクトテンプレートを選択してソリューションを作成します。

2.1 System.Windows.Interactivity.dll への 参照の追加

System.Windows.Interactivity.dll への参照を追加します。 ソリューションエクスプローラで プロジェクトを右クリック→参照の追加 をクリックします。 参照マネージャーが表示されるので、下図のように System.WIndows.Interactivity を選択して OK ボタンをクリックします。 

2.2 サンプルプログラムの実装

次に、既定で作成される MainWindow.xaml を編集します。サンプルとして次のようにしました。System.Windows.Interactivity ネームスペースのクラスを xaml で使用するので、Window タグに既定の名前空間に加え次のネームスペースを追加しています。また、サンプルでは EventTrigger ごとに1つの InvokeCommandAction を設定していますが、実際には複数の Action を記載できます。

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

<Window x:Class="EventCommandSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525" x:Name="win1">
    <StackPanel>
        <ListBox>
            <ListBoxItem>One</ListBoxItem>
            <ListBoxItem>Two</ListBoxItem>
            <ListBoxItem>Three</ListBoxItem>
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <i:InvokeCommandAction Command="{Binding SayHelloWorldCommand,ElementName=win1}"></i:InvokeCommandAction>
                </i:EventTrigger>
                <i:EventTrigger EventName="MouseLeave">
                    <i:InvokeCommandAction Command="{Binding SayGoodByeCommand,ElementName=win1}"></i:InvokeCommandAction>
                </i:EventTrigger>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding SelectCommand,ElementName=win1}"></i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListBox>
        <Label x:Name="lblMessage"></Label>
    </StackPanel>
</Window>

System.Windows.Interactivity名前空間の EventTrigger クラスの EventName に コマンドと関連付けるイベントを設定し、 InvokeCommandAction によりコマンドとの関連づけを行っています。上記サンプルではListBoxのMouseEnter, MouseLeave, SelectionChanged イベントでコマンドを実行するように設定ています。後述しますが、今回はサンプルを簡単にするため Window を継承したクラスにそのまま ICommand を実装したクラスをCommandプロパティとして定義しています。

MainWindow.xaml.cs にコマンド用のプロパティを作成します。Command クラスは次のように実装しました。

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private ICommand _sayHelloWorldCommand = null;
        /// <summary>
        /// Hello メッセージを表示するコマンド
        /// </summary>
        public ICommand SayHelloWorldCommand
        {
            get
            {
                if (_sayHelloWorldCommand == null)
                {
                    _sayHelloWorldCommand = new RelayCommand(new Action<object>( o => lblMessage.Content="Hello"));
                }
                return _sayHelloWorldCommand;
            }
        }

        private ICommand _sayGoodByeCommand = null;
        /// <summary>
        /// Good Bye メッセージを表示するコマンド
        /// </summary>
        public ICommand SayGoodByeCommand
        {
            get
            {
                if (_sayGoodByeCommand == null)
                {
                    _sayGoodByeCommand = new RelayCommand(new Action<object>(o =>  lblMessage.Content="Bye"));
                }
                return _sayGoodByeCommand;
            }
        }

        private ICommand _selectCommand = null;

        public ICommand SelectCommand
        {
            get
            {
                if (_selectCommand == null)
                {
                    _selectCommand = new RelayCommand(new Action<object>(o => MessageBox.Show("Selectet Changed")));
                }
                return _selectCommand;
            }
        }
    }

RelayCommand はMVVMパターンでよく紹介されているCommandManager.RequerySuggested を利用する方法で実装しています。

3.まとめ

説明は以上です。 前から知ってはいたのですが、Expression Blend SDK のInteractivity 名前空間のクラスを使用した コントロールのイベントと コマンドを関連付けるパターンをためしたかったのでやってみました。

EventTrigger で使用できるActionクラスはExpression Blend SDK にはその他 CallMethodAction, PlaySoundAction, StoryboardAction があるようです。 詳細は SDK のドキュメントを参照してください。

TriggerAction Generic Class の継承しているクラスの一覧を参照
http://msdn.microsoft.com/ja-jp/library/ff726548(v=expression.40).aspx