如何检测驻留在 ReactiveObject ViewModel 中的 ObservableCollection<T> 中类型 T 的任何项目的变化

How to detect has changes an any item of type T within ObservableCollection<T> that resides inside ReactiveObject ViewModel

我是 ReactiveUI 的新手,正在尝试检测 ReactiveObject 图表中的任何 属性 变化。

下面是一个简单的例子,其中Type1、Type2和Type3都是ReactiveObjects。

在顶级 Type1 实例中,当发生以下任何更改时,我将调用方法 CheckIfHasChanges:

  1. 新的 Type3 项目 added/updated 或已从 Type2 的 Type3s 集合中删除

  2. Type2 的 Type3s 集合中现有 Type3 项的名称 属性 更改

    public class Type1 : ReactiveObject
    {
        private ObservableCollection<Type2> _type2s = new ObservableCollection<Type2>();
    
        public Type1()
        {
            this.WhenAnyValue(x => x.Type2s)
                .Subscribe(_ => CheckIfHasChanges());
        }
    
        private void CheckIfHasChanges()
        {
           // Do something on change
        }
    
        public ObservableCollection<Type2> Type2s
        {
            get => _type2s;
            set => this.RaiseAndSetIfChanged(ref _type2s, value, nameof(Type2s));
        }
    }
    
    
    
    public class Type2:ReactiveObject
    {
        private ObservableCollection<Type3> _type3s = new ObservableCollection<Type3>();
    
        public ObservableCollection<Type3> Type3s
        {
            get => _type3s;
            set => this.RaiseAndSetIfChanged(ref _type3s, value, nameof(Type3s));
        }
    }
    
    public class Type3 : ReactiveObject
    {
        private string _name;
        public string Name
        {
            get => _name;
            set => this.RaiseAndSetIfChanged(ref _name, value, nameof(Name));
        }
    }
    
    
    class Program
    {
        static void Main(string[] args)
        {
            var t1 = new Type1();  // Initial call into Type1's CheckChanges happens here
    
            var t2 = new Type2();
    
            var t3 = new Type3 { Name = "First" };
            t2.Type3s.Add(t3);
    
            t1.Type2s.Add(t2);   // Would also like see call to Type1's CheckChanges method
            t3.Name = "Second";  // Would also like see call to Type1's CheckChanges method
        }
    }
    

这个问题的答案有点棘手,因为有很多移动目标,不仅需要观察集合变化,还需要监视其中的子集合和属性。

对于这样的任务,您可以使用与 RxUI 捆绑在一起的动态数据(我是作者)。动态数据基于 Rx,但具有丰富的扩展,可以实现对整个集合的观察和操作。

为了分解它,以免弄乱解决方案,我只会 post 摘录代码:

首先,添加命名空间:

using DynamicData;
using DynamicData.Binding;

Type2上添加如下内容属性(希望评论说明):

public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type3s)
    .SelectMany(t =>
    {
        //account for the dreaded null
        if (t == null) return Observable.Return(Unit.Default);

        //watch  the collection changes
        var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default);

        //watch the  property changes - use .WhenValueChanged(t=>t.Name, false) if you do not want the initial value and only want subsequent changes
        var propertyChanged = t.ToObservableChangeSet().WhenValueChanged(t=>t.Name).Select(_ => Unit.Default);

        //Combine the above and use StartWith(Unit.Default) so it always fires after Type3 is set
        return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default);
    });

对于 Type1 我们做了类似的事情但是我们使用了 Type2.HasAnythingChanged property

public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type2s)
    .SelectMany(t =>
    {
        //account for the dreaded null
        if (t == null) return Observable.Return(Unit.Default);

        //watch Type2 collection changes
        var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default);

        //watch for changes from Type2.HasAnythingChanged
        var propertyChanged = t.ToObservableChangeSet().MergeMany(x=>x.HasAnythingChanged).Select(_ => Unit.Default);

        return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default);
    });

最后把它们放在一起,你需要观察Type1的变化:

var hasAnythingChanged = t1.HasAnythingChanged
    .Subscribe(_=> Console.WriteLine("There has been a change"));

我还没有测试代码,但它应该可以工作。我建议您编写一些单元测试,首先检查 T2.HasAnythingChanged 是否在其子项更改时触发。如果你能正常工作,那么你可以将测试扩展到 Type1。

这里可能有很多新概念,但关键是:

ToObservableChangeSet() 将 observable 集合更改为 observable of changes。这将监控可观察集合中的添加、替换和删除。

MergeMany 合并基础集合中每个项目的可观察对象。随着项目的添加或删除,可观察对象会相应地连接/取消连接。

我在这里添加了一个要点以供参考https://gist.github.com/RolandPheasant/81dcc207bf95f6c4cf83f0ecd48ed740