コマンドをバインディングする

コマンド・バインディングってなに

前回の記事のデータ・バインディングで作ったサンプルでは、ボタンを押した時の処理はClickイベントを使って、コードビハインドで行っていました。

こんな感じで Xaml

<Button x:Name="CountButton" Click="CountUp_Click">Count Up!</Button>

コードビハインド

private void CountUp_Click(object sender, RoutedEventArgs e)
{
    //ボタンがクリックされた時の処理
    viewmodel.Count++;
}

このようなボタンを押した時などの処理をコマンドにバインディングして行うのが、コマンド・バインディングです。
イベントを使った場合はコードビハインドに処理を書いていましたが、コマンドを使う場合はViewModelに処理を書くというのが特徴です。

ここで私は、イベントの方が、HTMLとJavaScriptのような感覚で書けるから理解しやすいのに、なぜコマンドを使わなければいけないのかと思いました。

なぜコマンドをバインディングするの

コマンドを使うのには理由がありました。
それは、Viewにあるコントロール(Buttonなど)と、ViewModelで定義する処理(実際はコマンドを実装したクラスのプロパティ)を結び付けることができるからです。
ここの説明は後ほどします。

そもそもWPFMVVMパターンでは、コードビハインドには「VisualStudioが自動生成する以外のコードを書かない」といった基本方針があります。
そのため、MVVMパターンを用いた開発では、コードビハインドに処理を書くイベントは好まれないのです。

イベントの場合
f:id:marikooota:20170530233822p:plain

コマンドの場合
f:id:marikooota:20170530233845p:plain

コマンドをバインディングする方法を理解する

ではここからはコマンドをバインディングする方法を、コードと一緒に説明します。
まず、ViewModelにはViewにあるコントロールと結び付けるために、コマンドを実装したクラスのプロパティを用意します。

コマンドを実装したクラスの作り方

コマンドの実体はICommandインターフェースを実装したクラスです。
ICommandインターフェースは以下のようなメンバを持っており、コマンドを実装する時はこれらをオーバーライドする必要があります。

  • Executeメソッド
    コマンドを実行する。
  • CanExecuteメソッド
    コマンドが実行可能な状態にあるかどうかを判定する
  • CanExecuteChangedイベント
    NotifyPropertyChangedインターフェイスのPropertyChangedイベントと同様、コマンド実行の可否が変化したことを通知するためのイベント。

コマンドのサンプルコード

CountUpCommand.cs

public class CountUpCommand : ICommand
{
    private MainViewModel vm;

    public CountUpCommand(MainViewModel viewmodel)
    {
        vm = viewmodel;
    }

    public bool CanExecute(object parameter)
    {
        return vm.Count > 0;
    }

    public event EventHandler CanExecuteChanged;

    // コマンドが実行された時の処理
    public void Execute(object parameter)
    {
        vm.Count++;
    }
}
ViewModelに定義するプロパティの指定方法

ViewModelで定義するプロパティには先ほど作ったコマンドを実装したクラス(CountUpCommand)のプロパティを定義します。

MainViewModel.cs

public class MainViewModel : INotifyPropertyChanged
{
    //このプロパティをView上のコントロールと結び付ける
    public ICommand CountUp { get; set; }

    public MainViewModel()
    {
        // コマンドを実装したクラスをプロパティに代入
        CountUp = new CountUpCommand(this);
    }
//・・・以下省略
}
Xaml上のコントロールとViewModelのプロパティをバインディングする方法

Xamlには Command="{Binding プロパティ名}"と指定して、ViewModelのプロパティとバインディングすることを宣言します。
こうすることで、View上のボタンが押された時に、結び付いているViewModelのコマンドが実行されます。

MainWindow.xaml

<Button x:Name="CountButton" Command="{Binding CountUp}">Count Up!</Button>
コードビハインドには・・・

View上のコントロールとViewModelのコマンドを結び付けたことによって、コードビハインドにクリックされた時の処理を書かなくて済みます。
そのため、以下のように、DataContextの初期設定のみで良いので、余計なコードがなくシンプルです。

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private MainViewModel viewmodel;
    public MainWindow()
    {
        InitializeComponent();
        var viewmodel = new MainViewModel()
        {
            Count = 0
        };
        this.DataContext = viewmodel;
    }
}

補足:ICommandが実装してある仕組み

今回のようなICommandを使ったコマンド実装のやり方では、毎回用途に合わせてコマンドを実装しなければならず、めんどくさいです。
そこで、RelayCommandまたはDelegateCommandというクラスがあるのですが、これは.NET Frameworkの標準クラスではありません。
PrismやMVVMLightやLivetなどのMVVMフレームワークを使うとこのクラスが含まれています。

ですが、テスト的にRelayCommandの実装を確認したい場合は、ここを確認します。

RelayCommandの使い方はこんな感じです。
ViewModelのコンストラクタに記述してください。

MainViewModel.cs

CountUp = new RelayCommand(() => { Count++; });

独自に実装したコマンドとは違い、クリックされた時の処理をViewModel側で実装しています。
フレームワークを使う時はこっちを使った方が便利そうです。

まとめ
  • コマンドを使うとViewにあるコントロールと、ViewModelにある「コマンドを実装したクラスのプロパティ」を結び付けることができる
  • ICommandインターフェースを実装したクラスをコマンドとして扱う。

とても参考になったサイト
サンプルコードを見ながら理解するMVVMの基礎的な実装 - Neutral Scent
連載:WPF入門:第6回 「コマンド」と「MVVMパターン」を理解する (1/3) - @IT