tmori3y2のブログ

主にWindowsのプログラムなど

初心者が迷ったReactivePropertyを使用した数値型のView Modelの通知サイクルの改良

tmori3y2.hatenablog.com

前回のSubscribe()の実装の補足です。

今扱っているのは、表示や有効数字は小数点以下の桁数が決まっているけど、小数点以下の省略した0や、入力時の余計な0は良きに計らってくれるという、何とも贅沢なGUIの例です。

しかし、数値型をそのままViewにバインドした時は、固定桁数ならStringFormatで出来ちゃうので、実はそんなに贅沢ではないのですが、フォーマットした文字列をバインドしたときは、ちゃんと罠が用意されているという話です。(もちろん、数値型をそのままViewにバインドした時も別の罠があるのは言うまでもないですが・・・)

そもそも、数値型のReactiveProperty+MVVMパターンでは、以下の通知サイクルが回るようになっています。(表示や有効数字は小数点以下0桁だけど、入力時の余計な0は良きに計らってくれるGUIの例)

  1. M→VM(→V)→VM

    Modelに異なる値を入力した時:

    M:0m→M変更:1m→VM:"1"(→V:"1")→VM Stop(同値)

  2. M

    Modelに同じ値を入力した時:

    M:1m→M変更:1m→M Stop(同値)

  3. V→VM→M→VM

    Viewに異なる値を入力した時/VMでの再フォーマット不要:

    V:"0"→V変更:"5"→VM:"5"→M:5m→VM Stop(同値)

  4. V→VM→M→VM(→V)→VM

    Viewに異なる値を入力した時/VMでの再フォーマット必要:

    V:"0"→V変更:"5.0"→VM:"5.0"→M:5m→VM:"5"(→V:"5")→VM Stop(同値)

  5. V→VM

    Viewに同じ値を入力した時:

    V:"2"→V変更:"2"→VM Stop(同値)

  6. V→VM→M→VM

    Viewに異なる値を入力したがModelが変わらない時

    V:"2"→V変更:"2.0"→VM:"2.0"→M Stop(同値)

    (VMでの再フォーマット必要だが途中で止まる)

通知サイクルは、ModelかView Modelの値が変わらなくなった時に停止します。

最後のパターン6は、再フォーマットが必要なのに途中で止まってしまうので、入力したままの文字列が表示されるだけです。

6のパターンを、4のパターンに変更するためには、以下の追加の実装が必要でした。

3~5の場合は、Subscribe()の1行目のmodel.Decimals.Value = iを実行することで、2行目は何もせずに終了するので、パターンに変化はありません。

6の場合は、Subscribe()の1行目は何もせずに終了しますが、2行目で目論見通り4のパターンの処理を継続してくれます。

なお、他の方法ですが、以下の理由で不採用としました。

  • ModelのReactivePropertyのコンストラクタでDistinctUntilChangedの指定をやめて同値チェックを無くす
    • Modelの同値チェックが無くなるとView Modelでしかチェックできなくなるので通知サイクルが長くなる
    • Modelを別のReactivePropertyが監視している時に副作用を起こす(今回の事例では同値でもIsDirtyが立つ)
    • そもそもModelを通常のPOCOにした時はReactivePropertyの機能はあてにできない
  • Modelが通常のPOCOの場合は強制通知機能を実装してModelの強制通知機能を呼び出す
    • BindableBaseとか、通知メソッドはprotectedなので、いちいちpublicなメソッドの実装が必要
    • Modelを別のReactivePropertyが監視している時に副作用を起こす(今回の事例では同値でもIsDirtyが立つ)
    • 「普通のModelクラスでガンバレ」って時点でReactivePropertyをわざわざ使うモチベーションが半減

2016/02/05追記, 2016/02/07修正:

は、Modelの値をフォーマットし直して表示するためには必要悪とは言え、それほど珍しくもないdecimalのパターンでの実装としては、毎回変換している辺りが、イマイチいけていない・・・

model.Decimals.Value.Convert()は、ToReactiveProperty()の実態のコンストラクタを見ると、sourceに対応している筈です。

ReactiveProperty.cs

数値が同じだがフォーマットの違う文字列を入力し続けると、SetValue()でLatestValueがViewに上書きされ続けるので、sourceの最後の値を取り直すしか無いのですが、今のところ利用する手立てはありません。