kazuakix の日記

Windows Phone とか好きです

コマンド入門してみる

みなさんは Windows Phone アプリのイベント処理ってどうしていますか?

例えば こんな感じでボタンを推したら数字が増えるような画面の場合...

f:id:kazuakix:20141108174126j:plain,w240

すべてを画面で?

一番簡単に考えると画面 (ビュー) のイベント ハンドラを書きますよね。

<TextBlock x:Name="Val" Text="0" />
<Button Content="Add" Click="OnAddButtonClicked"/>
private int _val;
private void OnAddButtonClicked(object sender, RoutedEventArgs e)
{
    this._val ++;
    this.Val.Text = this._val.ToString();
}

でも、何でもかんでも画面に持たせると保守性も拡張性もないプログラムになっていくので データ部分をビュー モデルやモデルに分割する流れになります。

データをビューモデルに

という訳でビュー モデル。値の変化を伝えるための INotifyPropertyChanged 関係のコード *1 でかなり冗長に見えますが、中身は値を保持するプロパティと加算するという動作だけです。

public class MainPageViewModel : INotifyPropertyChanged
{
    private int _val;
    public int Val
    {
        get { return this._val; }
        set { this._val = value; NotifyPropertyChanged("Val"); }
    }

    public void AddVal()
    {
        this.Val ++;
    }    

    #region INotifyPropertyChanged 関係
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion    
}

 
あとはビューのコード ビハインドで ビューモデルを生成して、イベント ハンドラの中身もビュー モデルに対する操作に書き換えます。

public sealed partial class MainPage : Page
{
    private MainPageViewModel _viewModel = new MainPageViewModel();
    public MainPageViewModel ViewModel
    {
        get { return this._viewModel; }
    }

    public MainPage()
    {
        this.InitializeComponent();
    }

    private void OnAddButtonClicked(object sender, RoutedEventArgs e)
    {
        this.ViewModel.AddVal();
    }
}

 
ビュー モデルは XAML で DataContext にバインドしています。

<Page
    (中略)
    DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}">
    (中略)
    <TextBlock Text="{Binding Val}" />
    <Button Content="Add" Click="OnAddButtonClicked"/>
    (中略)    
</Page>

TextBlock に名前をつける必要もなくなって XML も少しだけシンプルになりますね。
でも、コード ビハインドのイベント ハンドラは残ったままです。ついついここにコードを書いて結局は分割が不完全になったりしますよね? (あ、僕だけですか?)
 

コマンドで動作も切り離す

ようやく今日の本題。データだけでなく動作も切り離してビューのコード ビハインドをスッキリさせます。

参考にしたのはコチラの動画。

コマンド用のクラスを作成

ICommand インタフェースを継承したクラスを作成します。

public class AddButtonClick : ICommand
{
    public bool CanExecute(object parameter)
    {
        //throw new NotImplementedException();
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        throw new NotImplementedException();
    }
}

CanExecute メソッドはコマンドを実行できるかどうかを返すのですが、ひとまずは常に true を返しておきます。CanExecuteChanged はこのコマンドの実行可・不可が切り替わったときに呼び出されるイベントです。今回は何もしません。
Execute がコマンドの実行時に呼びされるメソッドですが、今はは保留にしておきます。

ビュー モデルに追加

このコマンド用クラスをビュー モデルのプロパティに追加します。 

public class MainPageViewModel : INotifyPropertyChanged
{
    public ICommand AddValCommand { get; set; }
    
    public MainPageViewModel()
    {
        this.AddValCommand = new AddButtonClick();
    }

ビューからの呼び出し

これでビューにバインドできるようになったので、XAML を修正します。

<TextBlock Text="{Binding Val}" />
<Button Content="Add" Command="{Binding AddValCommand}" CommandParameter="{Binding}"/>

Command にビュー モデルで公開した AddValCommand を、CommandParameter にこのビューの DataContext (つまりは ビュー モデル) を指定しています。

これでコマンド呼び出し時に引数としてビュー モデルがわたってくるので、加算のメソッドが呼べるようになります。

    public void Execute(object parameter)
    {
        //throw new NotImplementedException();        
        ((MainPageViewModel)parameter).AddVal();
    }

 

まとめ

「なんだよ、コード超長くなってるじゃんか」 なんて思ってしまいますが、プログラムがある程度大きくなってきたらこちらの方が楽になる...はずです。

と言いつつ、このままだとコマンド用のクラスが際限なく増えていってしまうのでその対策はまた改めて。

*1:もうちょっとマシな書き方もあります