ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

ViewModelをシンプルに書きたい! -①ReactiveProperty編-

最近、仕事でも少しづつWPFアプリを作る機会が増えてきました。
でもViewModel書くのがめんどいと、ちょっとWinFormsが恋しくもなるんですよねw
なので、できるだけシンプルにViewModel書けないかなーと試行錯誤してます。
せっかくなんでブログに書いていきます。

何はなくともReactiveProperty

これいいですね。VMシンプルに書けます。INotifyPropertyChangedを実装しなくていい。ちょっと例として、ボタンを押したらテキストボックスの数値をインクリメントするプログラムを実装してみます。
f:id:ishikawa-tatsuya:20160611093916p:plain
こんな感じで実装できます。

using Reactive.Bindings;
using System;
using System.Reactive.Linq;

namespace WpfApp
{
    public class MainWindowVM
    {
        public ReactiveProperty<string> Number { get; } = new ReactiveProperty<string>("0");
        public ReactiveCommand IncrementCommand { get; } = new ReactiveCommand();

        public MainWindowVM()
        {
            //コマンドが実行された通知を購読
            IncrementCommand.Subscribe((_) => Increment());
        }

        public void Increment()
        {
            Number.Value = (int.Parse(Number.Value) + 1).ToString();
        }
    }
}
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApp"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowVM/>
    </Window.DataContext>

    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBox Width="100" Text="{Binding Number.Value, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="インクリメント" Command="{Binding IncrementCommand }"/>
        </StackPanel>
    </StackPanel>
</Window>

いつからか、普通のイベント接続も「購読」って言われるようになってますよねw(あれホンとにいつからの習慣なんだろ?)
なんでINotifyPropertyChangedを実装しなくてもいいでしょう?ポイントはValueでBindingすることです。

<TextBox Width="100" Text="{Binding Number.Value, UpdateSourceTrigger=PropertyChanged}"/>

ViewModel自体がINotifyPropertyChanged実装しなくても、プロパティーを持っているインスタンスがINotifyPropertyChangedを実装してたらちゃんとイベント接続してくれるみたいですね。まあ考えてみたらObservableCollectionとかそうですよね。でも知らんかった。そこだけなら、こんな感じのクラスで代用できますね。

public class Notify<T> : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = (_, __) => { };

    T _value;
    public T Value
    {
        get { return _value; }
        set
        {
            var hasDiff = _value == null ? value != null : !_value.Equals(value);
            if (hasDiff)
            {
                _value = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Value"));
            }
        }
    }

    public Notify() { }
    public Notify(T value) { _value = value; }
}

CanExecuteの制御

でも、これだとテキストボックスに数値変換できない文字列が入ってたら例外終了しちゃいますね。そんなときはコマンドを無効にできます。しかもそれがシンプルに書けます。

using Reactive.Bindings;
using System;
using System.Reactive.Linq;

namespace WpfApp
{
    public class MainWindowVM
    {
        public ReactiveProperty<string> Number { get; } = new ReactiveProperty<string>("0");
        public ReactiveCommand IncrementCommand { get; }

        public MainWindowVM()
        {
            //値の変更があるたびにSelectが呼び出される
            //IObservable<string> → IObservable<bool>に変換 →それをつかったReactiveCommand
            //CanExecuteはそのboolが使われ、値の変化ごとにCanExecuteChangedも呼び出される
            var num = 0;
            IncrementCommand = Number.Select(e => int.TryParse(e, out num)).ToReactiveCommand();

            //コマンドが実行された通知を購読
            IncrementCommand.Subscribe((_) => Increment());
        }

        public void Increment()
        {
            Number.Value = (int.Parse(Number.Value) + 1).ToString();
        }
    }
}

f:id:ishikawa-tatsuya:20160611095605p:plain
シンプルさが保たれていますね。

真骨頂は本家の人たちのブログで

これだけでも十分便利なんですが、真骨頂はMVVM間をシームレスにReactiveにつなぐことなんだそうです。詳細は本家の人たちのブログを参照してくださいねー。
blog.okazuki.jp