如何检测驻留在 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:
新的 Type3 项目 added/updated 或已从 Type2 的 Type3s 集合中删除
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
我是 ReactiveUI 的新手,正在尝试检测 ReactiveObject 图表中的任何 属性 变化。
下面是一个简单的例子,其中Type1、Type2和Type3都是ReactiveObjects。
在顶级 Type1 实例中,当发生以下任何更改时,我将调用方法 CheckIfHasChanges:
新的 Type3 项目 added/updated 或已从 Type2 的 Type3s 集合中删除
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