XAMLからViewModelのメソッドにバインドする~RelayCommand~
RelayCommand/DelegateCommand
WPFなどのXAMLを用いたフレームワークでは、Commandを使ってXAMLからViewModelのメソッドを呼び出すためには、DelegateCommandとかRelayCommandと呼ばれるクラスを作ります。で、VMにこのコマンドのインスタンスを作成してXAML側からバインドします。
この辺の説明は、以下のサイトに詳しく説明されてます。
http://www.atmarkit.co.jp/ait/articles/1011/09/news102_3.html
話は変わりますが、Windows8.1のストアアプリ用のプロジェクトのテンプレートには、
RelayCommandというものが追加されてました。
ということで、このRelayCommandをWPFでも使用してみたいと思います。
というか、このクラス。そのままWPFのプロジェクトにコピペして使用できます。
Windows8.1ストアアプリのRelayCommandをWPFで使ってみる
まずは、↓の画像のようなプロジェクトを用意します。
MainWindow一つと、このウィンドウ用のVMとなるMainWindowViewModelクラスを追加。
あと、Commonフォルダには、Win8.1ストア用のRelayCommandクラスとBindableBaseクラスを追加します。
BindableBaseは、↓の記事で書いたWin8ストアアプリ用のコードを流用したものです。
http://sourcechord.hatenablog.com/entry/20130303/1362315081
サンプルコード
BindableBaseは前述の記事のコードのまま。
RelayCommandはWin8.1のコードをそのままコピペなので省略します。
ボタンを押したら、VMのコマンドを実行してTextBlockの文字列を変更するだけのサンプルです。
MainWindow.xaml
<Window x:Class="RelayCommandTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300" Height="200"> <Grid> <TextBlock Margin="10,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Message}" TextWrapping="Wrap" /> <Button Width="75" Margin="10,30,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ChangeMessageCommand}" Content="Button" /> </Grid> </Window>
MainWindow.xaml.cs
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); } }
MainWindowViewModel.cs
class MainWindowViewModel : BindableBase { private string message; public string Message { get { return message; } set { this.SetProperty(ref this.message, value); } } #region コマンドの実装 private RelayCommand changeMessageCommand; public RelayCommand ChangeMessageCommand { get { return changeMessageCommand = changeMessageCommand ?? new RelayCommand(ChangeMessage); } } private void ChangeMessage() { this.Message = "コマンドを実行しました。"; } #endregion public MainWindowViewModel() { this.Message = "初期値です。"; } }
任意の型の引数を受け付けるようにする
Win8.1のRelayCommandは、引数なしのAction型のメソッドを実行するのみで、CommandParameterとして渡される引数を使用することはできません。
そこで、↓の記事を参考に、任意の型の引数を受け付けるRelayCommand
http://okazuki.hatenablog.com/entry/20100223/1266897125
RelayCommand.cs
通常のRelayCommandの後に、RelayCommand
/// <summary> /// その機能を中継することのみを目的とするコマンド /// デリゲートを呼び出すことにより、他のオブジェクトに対して呼び出します。 ///CanExecute メソッドの既定の戻り値は 'true' です。 /// <see cref="RaiseCanExecuteChanged"/> は、次の場合は必ず呼び出す必要があります。 /// <see cref="CanExecute"/> は、別の値を返すことが予期されます。 /// </summary> public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _canExecute; /// <summary> /// RaiseCanExecuteChanged が呼び出されたときに生成されます。 /// </summary> public event EventHandler CanExecuteChanged; /// <summary> /// 常に実行可能な新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> public RelayCommand(Action execute) : this(execute, null) { } /// <summary> /// 新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> /// <param name="canExecute">実行ステータス ロジック。</param> public RelayCommand(Action execute, Func<bool> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } /// <summary> /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns> public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(); } /// <summary> /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> public void Execute(object parameter) { _execute(); } /// <summary> /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド /// <see cref="CanExecute"/> の戻り値を表すために /// メソッドが変更されました。 /// </summary> public void RaiseCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { handler(this, EventArgs.Empty); } } } /// <summary> /// 任意の型の引数を1つ受け付けるRelayCommand /// </summary> /// <typeparam name="T"></typeparam> public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Func<T, bool> _canExecute; /// <summary> /// RaiseCanExecuteChanged が呼び出されたときに生成されます。 /// </summary> public event EventHandler CanExecuteChanged; /// <summary> /// 常に実行可能な新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> public RelayCommand(Action<T> execute) : this(execute, null) { } /// <summary> /// 新しいコマンドを作成します。 /// </summary> /// <param name="execute">実行ロジック。</param> /// <param name="canExecute">実行ステータス ロジック。</param> public RelayCommand(Action<T> execute, Func<T, bool> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } /// <summary> /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns> public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute((T)parameter); } /// <summary> /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。 /// </summary> /// <param name="parameter"> /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。 /// </param> public void Execute(object parameter) { _execute((T)parameter); } /// <summary> /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド /// <see cref="CanExecute"/> の戻り値を表すために /// メソッドが変更されました。 /// </summary> public void RaiseCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { handler(this, EventArgs.Empty); } } }
RelayCommand+任意の型の引数のサンプル
同一のコマンドにバインドしたButtonを三つ配置し、それぞれのボタンに別々のCommandParameterを設定しています。
で、コマンドを実行したら、CommandParameterの内容を表示するサンプルです。
MainWindow.xaml
<Window x:Class="RelayCommandTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300" Height="200"> <Grid> <TextBlock Margin="10,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Message}" TextWrapping="Wrap" /> <Button Width="75" Margin="10,30,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ChangeMessageCommand}" CommandParameter="ボタン1" Content="Button1" /> <Button Width="75" Margin="10,54,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ChangeMessageCommand}" CommandParameter="ボタン2" Content="Button2" /> <Button Width="75" Margin="10,78,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding ChangeMessageCommand}" CommandParameter="ボタン3" Content="Button3" /> </Grid> </Window>
MainWindowViewModel.cs
class MainWindowViewModel : BindableBase { private string message; public string Message { get { return message; } set { this.SetProperty(ref this.message, value); } } #region コマンドの実装 private RelayCommand<string> changeMessageCommand; public RelayCommand<string> ChangeMessageCommand { get { return changeMessageCommand = changeMessageCommand ?? new RelayCommand<string>(ChangeMessage); } } private void ChangeMessage(string parameter) { this.Message = "CommandParameter: " + parameter ; } #endregion public MainWindowViewModel() { this.Message = "初期値です。"; } }