かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

ReactivePropertyで、ViewModelのプロパティに変更があったかどうか判別できるようにしたい

プロパティの値が1つでも書き換わってたら判別したい。そういうことはありますよね? ReactivePropertyを使うと、こんな感じで実現できます。(変更があったかというフラグをリセットする機能つき)

まず、ReactivePropertyに変更があったときにtrueを発行するIObservableに変換するメソッドを作っておきます。

static class MvvmHelpersExtensions
{
    public static IObservable<bool> ChangedAsObservable<T>(this ReactiveProperty<T> self, bool skipFirst = true)
    {
        var result = self.AsObservable();
        if (skipFirst)
        {
            result = result.Skip(1);
        }
        return result.Select(_ => true);
    }
}

このメソッドを使うと、ReactivePropertyに変更があったときにtrueがくるので、必要なプロパティの数だけMergeしてReadOnlyReactiveProperty(初期値false)あたりにしてしまえばOKです。そのとき、Subjectを渡して任意の値にセットできるようにしておくと、リセットにも対応できます。

class MountainViewModel
{
    private Subject<bool> ResetIsDirtySubject { get; } = new Subject<bool>();
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
    public ReactiveProperty<double> HeightInMeters { get; } = new ReactiveProperty<double>();
    public ReadOnlyReactiveProperty<bool> IsDirty { get; }

    public MountainViewModel()
    {
        this.IsDirty = Observable.Merge(
            this.Name.ChangedAsObservable(),
            this.HeightInMeters.ChangedAsObservable(),
            this.ResetIsDirtySubject)
            .ToReadOnlyReactiveProperty(false);
    }

    public void ResetDirty()
    {
        this.ResetIsDirtySubject.OnNext(false);
    }
}