じゃがいも畑

開発ネタの記録

ReactivePropertyの便利メソッド(ToReactivePropertyとToReactivePropertyAsSynchronized)

名前が長くて呼び出し方をよく忘れてしまいますが、ViewModelとModelで片方向・双方向バインドができてとても便利です

なるべく短くまとめたいと思います

ToReactiveProperty系メソッド

  • ToReactiveProperty
  • ToReadOnlyReactiveProperty
  • ToReadOnlyReactivePropertySlim

Modelの値が変更されたとき、ViewModelへの通知を行います(片方向)

こんな感じでボタンを押すとTextBlockに値のカウントを増やす動きを作ります

f:id:whitedog0215:20200317012102g:plain

まずはサンプルコード

  • ViewのButtonとTextBlock
<Button Width="100" Height="40" Content="Button" Command="{Binding ButtonCommand}" FontSize="20"/>
<TextBlock Text="{Binding Counter.Value}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
  • ModelとViewModel
    public class Model : BindableBase
    {
        private int pushCount;
        public int PushCount
        {
            get { return pushCount; }
            set { SetProperty(ref pushCount, value); }
        }
    }

    public class MainWindowViewModel : BindableBase
    {
        public ReactiveCommand ButtonCommand { get; } = new ReactiveCommand();
        public ReadOnlyReactivePropertySlim<int> Counter { get; }
        public MainWindowViewModel()
        {
            var model = new Model();

            this.ButtonCommand.Subscribe(() =>
            {
                model.PushCount++;
            });

            this.Counter = model.ObserveProperty(x => x.PushCount).ToReadOnlyReactivePropertySlim();
        }
    }
  • 大事なポイント
this.PushCount = model.ObserveProperty(x => x.PushCount).ToReadOnlyReactivePropertySlim();

ModelはINotifyChangedを継承する必要があります(BindableBaseはINotifyChangedを継承したPrismのクラス)

ObservePropertyから変更通知を受け取りたいプロパティを指定してToReactivePropertyします

こうすることで

  1. ボタンが押されたらModel.PushCountの値を変更
  2. Model.PushCountがViewModelCounterへ変更を通知
  3. 表示の更新

という動きが実現できます

ToReactivePropertyAsSynchronized

  • ViewModelの値が変更された時、Modelに変更の通知
  • Modelの値が変更された時、ViewModelに変更の通知

を行います(双方向)

f:id:whitedog0215:20200317215148g:plain

  • TextBoxに入力した文字と同じ文字をTextBlockに表示
  • VMボタンを押すとVMと入力
  • Mボタンを押すとMと入力

サンプルコード

  • Viewのそれぞれ
<TextBox Width="200" Height="40" Text="{Binding Input.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="20"/>
<TextBlock Text="{Binding Output.Value}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="40"/>
<Button Width="100" Height="40" Content="VM" Command="{Binding ViewModelChangeCommand}" FontSize="20"/>
<Button Width="100" Height="40" Content="M" Command="{Binding ModelChangeCommand}" FontSize="20"/>
  • ModelとViewModel
    public class Model : BindableBase
    {
        private string input;
        public string Input
        {
            get { return input; }
            set { SetProperty(ref input, value); }
        }
    }

    public class MainWindowViewModel : BindableBase
    {
        public ReactiveProperty<string> Input { get; }
        public ReadOnlyReactivePropertySlim<string> Output { get; }
        public ReactiveCommand ViewModelChangeCommand { get; } = new ReactiveCommand();
        public ReactiveCommand ModelChangeCommand { get; } = new ReactiveCommand();
        public MainWindowViewModel()
        {
            var model = new Model();

            this.Input = model.ToReactivePropertyAsSynchronized(x => x.Input);

            this.Output = model.ObserveProperty(x => x.Input).ToReadOnlyReactivePropertySlim();

            this.ModelChangeCommand.Subscribe(() =>
            {
                model.Input = "M";
            });

            this.ViewModelChangeCommand.Subscribe(() =>
            {
                this.Input.Value = "VM";
            });
        }
    }
  • 大事なポイント
this.Input = model.ToReactivePropertyAsSynchronized(x => x.Input);

INotifyChangedを継承したModelからToReactivePropertyAsSynchronizedを呼び出し、双方向バインドしたいプロパティを指定します

こうすることで

  1. TextBoxに文字を入力するとViewModel.Inputの値が変更
  2. ViewModel.InputがModel.Inputに変更を通知
  3. Model.InputがViewModel.Outputに変更を通知(表示の更新)

という動きができています。さらにボタンを押したときには

  • Mボタンの場合

  • ボタンが押されたらModel.Inputの値を変更

  • Model.InputがViewModel.InputとViewModel.Outputに変更を通知(TextBoxとTextBlockの表示更新)

  • VMボタンの場合

  • ボタンが押されたらViewModel.Inputの値を変更(TextBoxの表示更新)

  • ViewModel.InputがModel.Inputに変更を通知
  • Model.InputがViewModel.Outputに変更を通知(TextBlockの表示の更新)

という動きになります

わざわざモデルの値を見に行く必要がなくなるので便利