kazuakix の日記

Windows Phone とか好きです

Prism + ReactiveProperty 入門 : ViewModel のプロパティをバインドしてみる

Prism を使って自分の汚い設計を矯正していこうシリーズです。


少し軌道修正して Prism + ReactiveProperty で進めてみたいと思います。*1

準備

Prism.StoreApps に加えて ReactiveProperty をインストールします。

f:id:kazuakix:20150222220410j:plain,w500

現在のバージョンは 1.2.1 ですが、時代は既に 2.0 系なんだそうです。(id:okazuki 氏談)

Model を作る

適当な題材として WP7 時代につくった IPv4 のサブネット計算を持ってきました。

Prism の BindableBase (中身は INotifyPropertyChanged の実装) を継承して、それぞれのプロパティが変更を通知できるようにしています。

public class Cidr : BindableBase
{
    /// <summary>IP アドレス</summary>
    public string IpAddress
    {
        get { return _ipAddres; }
        set
        {
            SetProperty(ref _ipAddres, value);
            Calc();
        }
    }
    /// <summary>サブネットマスク</summary>
    public string SubnetMask
    {
        get { return _subnetMask; }
        set
        {
            SetProperty(ref _subnetMask, value);
            Calc();
        }
    }

    /// <summary>ネットワークアドレス</summary>
    public string NetworkAddress
    {
        get { return _networkAddress; }
        private set { SetProperty(ref _networkAddress, value); }
    }
    /// <summary>使用可能範囲</summary>
    public string UsableRange
    {
        get { return _usableRange; }
        private set { SetProperty(ref _usableRange, value); }
    }
    /// <summary>ブロードキャストアドレス</summary>
    public string BroadcastAddress
    {
        get { return _broadcastAddress; }
        private set { SetProperty(ref _broadcastAddress, value); }
    }
    /// <summary>使用可能数</summary>
    public ulong  UsableCount
    {
        get { return _usabeleCount; }
        private set { SetProperty(ref _usabeleCount, value); }
    }
    /// <summary>ワイルドカード</summary>
    public string WildCard
    {
        get { return _wildCard; }
        private set { SetProperty(ref _wildCard, value); }
    }

    // 使用可能アドレスを計算する
    private void Calc()
    {
      // IP アドレス とサブネットマスクから使用可能な範囲を計算
    }
}

この書き方は半ばお約束でほぼコピペの世界ですが、IP アドレスとサブネットマスクだけは変更されたときにサブネットマスク計算を行う Calc メソッドを呼ぶようにしています。(ちょっと乱暴ですね)

ViewModel

ここからが本番。ViewModel は Prism のその名も ViewModel を継承して作ります。

そして Reactive Property を使って Model の各プロパティをマッピングしています。

このとき、入出力が必要なプロパティは ToReactivePropertyAsSynchronized()、出力だけでいいプロパティは ObserveProperty().ToReactiveProperty() しています。

class MainPageViewModel : ViewModel
{
    public Cidr CidrModel { get; private set; } // Model

    /// <summary>ホストアドレス</summary>
    public ReactiveProperty<string>     IpAddress        { get; private set; }
    /// <summary>サブネットマスク</summary>
    public ReactiveProperty<string>     SubnetMask       { get; private set; }

    /// <summary>ネットワークアドレス</summary>
    public ReactiveProperty<string>     NetworkAddress   { get; private set; }
    /// <summary>使用可能範囲</summary>
    public ReactiveProperty<string>     UsableRange      { get; private set; }
    /// <summary>ブロードキャストアドレス</summary>
    public ReactiveProperty<string>     BroadcastAddress { get; private set; }
    /// <summary>使用可能数</summary>
    public ReactiveProperty<ulong>      UsableCount      { get; private set; }
    /// <summary>ワイルドカード</summary>
    public ReactiveProperty<string>     WildCard         { get; private set; }

    public MainPageViewModel()
    {
        CidrModel = new Cidr();

        // 双方向系
        IpAddress       = CidrModel.ToReactivePropertyAsSynchronized(c => c.IpAddress);
        SubnetMask      = CidrModel.ToReactivePropertyAsSynchronized(c => c.SubnetMask);

        // 一方通行系
        NetworkAddress   = CidrModel.ObserveProperty(c => c.NetworkAddress).ToReactiveProperty();
        UsableRange      = CidrModel.ObserveProperty(c => c.UsableRange).ToReactiveProperty();
        BroadcastAddress = CidrModel.ObserveProperty(c => c.BroadcastAddress).ToReactiveProperty();
        UsableCount      = CidrModel.ObserveProperty(c => c.UsableCount).ToReactiveProperty();
        WildCard         = CidrModel.ObserveProperty(c => c.WildCard).ToReactiveProperty();
    }
}

.

View

あとはこの ViewModel を画面にセットするだけですが、各プロパティの Value プロパティをバインドする必要がある点に注意する必要があります。

<TextBox Text="{Binding IpAddress.Value,  Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding SubnetMask.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}/>

<TextBlock TextWrapping="Wrap" Text="{Binding NetworkAddress.Value}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding UsableRange.Value}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding BroadcastAddress.Value}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding UsableCount.Value}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding WildCard.Value}"/>


動かしてみるとこんな感じ。素直に動いてくれました。

f:id:kazuakix:20150222223650j:plain,w360

ちょっと困っている点

View では ReactiveProperty の Value プロパティをバインドする事になるのですが、Blend のサンプルデータ機能と相性が悪いようです。

f:id:kazuakix:20150222224551j:plain

適当なサンプルデータを生成しようとしたのですが、何も作ってくれませんでした。
サンプルデータの XAML を直接書く必要があるのかもしれません。

*1:脅されたので