如何检测驻<T>留在 ReactiveObject ViewModel 中的可观察集合中 T 类型的任何项的更改



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

下面是一个简单的例子,Type1, Type2和Type3都是reactiveobject。

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

  1. 从Type2的Type3集合中添加/更新或删除一个新的Type3项

  2. Type2的Type3集合中已有的Type3项的Name属性发生变化

    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的,但是有丰富的扩展,可以对整个集合进行观察和操作。

为了分解它,为了不使解决方案混乱,我将只发布代码的摘录:

首先,添加名称空间:
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);
});

类型1我们做了类似的事情,但我们利用了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"是指孩子的变化。如果你让它工作,那么你可以扩展你的测试类型1。

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

ToObservableChangeSet()将可观察集合改为变化的可观察对象。这将监视可观察对象集合的添加、替换和删除。

MergeMany,它合并了底层集合中每个项目的可观察对象。当项被添加或删除时,可观察对象会相应地被连接/断开连接。

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