如何使用 ReactiveUI 和 DynamicData 将可变模型的 ObservableCollection<T> 绑定到视图模型的 ReadOnlyObservableCollection<T>
How to bind ObservableCollection<T> of mutable model to ReadOnlyObservableCollection<T> of viewmodel using ReactiveUI and DynamicData
我在我的 C# 项目中使用 ReactiveUI 和 DynamicData。但是,域模型 类 仍然依赖于 C# 事件、INotifyPropertyChanged 和 INotifyCollectionChanged 接口。
有Model和ViewModel类:
public class Model
{
public ObservableCollection<int> Collection { get; } = new ObservableCollection<int>();
}
public class ViewModel : ReactiveObject, IDisposable
{
private readonly CompositeDisposable _cleanUp;
private readonly SourceList<int> _collectionForCurrentBModel = new SourceList<int>();
private Model _model = new Model();
private IDisposable _tempCleanUp = Disposable.Empty;
public ViewModel()
{
_cleanUp = new CompositeDisposable();
_collectionForCurrentBModel.Connect()
.Bind(out var aModelsForCurrentBModel)
.Subscribe(Console.WriteLine)
.DisposeWith(_cleanUp);
CollectionForCurrentBModel = aModelsForCurrentBModel;
this.WhenAnyValue(x => x.Model.Collection) // Every time Model in ViewModel changes:
.Subscribe(collection =>
{
// we dispose previous subscription:
_tempCleanUp.Dispose();
// then we manually reset SourceList<int> to match new collection:
_collectionForCurrentBModel.Edit(x =>
{
x.Clear();
x.AddRange(collection);
});
// finally, we manually subscribe to ObservableCollection<int>'s events to synchronize SourceList<int>.
_tempCleanUp = collection.ObserveCollectionChanges().Subscribe(pattern =>
{
switch (pattern.EventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
_collectionForCurrentBModel.AddRange(pattern.EventArgs.NewItems.Cast<int>());
break;
case NotifyCollectionChangedAction.Remove:
_collectionForCurrentBModel.RemoveRange(pattern.EventArgs.OldStartingIndex,
pattern.EventArgs.OldItems.Count);
break;
case NotifyCollectionChangedAction.Replace:
for (var i = 0; i < pattern.EventArgs.NewItems.Count; i++)
_collectionForCurrentBModel.Replace((int) pattern.EventArgs.OldItems[i],
(int) pattern.EventArgs.NewItems[i]);
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new ArgumentOutOfRangeException();
}
});
});
}
public ReadOnlyObservableCollection<int> CollectionForCurrentBModel { get; }
public Model Model
{
get => _model;
set => this.RaiseAndSetIfChanged(ref _model, value);
}
public void Dispose()
{
_cleanUp.Dispose();
}
}
所以,ViewModel 有模型 属性。当前模型可以更改为另一个。 ViewModel 也有 CollectionForCurrentModel 属性,在这个例子中它基本上等于它的来源(Model.Collection)(但是,应该有一些排序,过滤等)。 CollectionForCurrentModel 属性 应该是只读的。
下面的代码按预期工作:
private static void Main(string[] args)
{
using var viewModel = new ViewModel();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(0);
// viewModel.Collection: {0}
viewModel.Model.Collection.Add(1);
// viewModel.Collection: {0, 1}
var oldModel = viewModel.Model;
viewModel.Model = new Model();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(2);
// viewModel.Collection: {2}
oldModel.Collection.Add(3);
// viewModel.Collection: {2}
}
但是,在ViewModel中添加新的字段来存储最新的订阅,手动取消订阅和手动同步集合看起来很丑陋。还有其他订阅方式吗:
IObservable<IObservable<IChangeSet<T>>>
\ is result of
this.WhenAnyValue(x => x.ObservableCollection, selector: collection => collection.ToObservableChangeSet();
? DynamicData 能否自动管理内部订阅以将可变 属性 中的可观察集合绑定到其他集合?
这应该很容易。你可以这样做:
this.WhenAnyValue(x => x.Model.Collection)
.Select(collection => collection.ToObservableChangeSet())
.Switch() //this is the dynamic data overload of rx.Switch()
.Bind(out var aModelsForCurrentBModel)
.Subscribe();
select 语句 returns 可观察的更改集的可观察,它本身不是很有用。这就是为什么需要 switch 语句的原因。设置集合后,它会从缓存中清除现有项目并从新的可观察集合中加载项目。之后,您可以简单地绑定到目标可观察集合。
使用此技术意味着无需手动维护源列表。
我在我的 C# 项目中使用 ReactiveUI 和 DynamicData。但是,域模型 类 仍然依赖于 C# 事件、INotifyPropertyChanged 和 INotifyCollectionChanged 接口。
有Model和ViewModel类:
public class Model
{
public ObservableCollection<int> Collection { get; } = new ObservableCollection<int>();
}
public class ViewModel : ReactiveObject, IDisposable
{
private readonly CompositeDisposable _cleanUp;
private readonly SourceList<int> _collectionForCurrentBModel = new SourceList<int>();
private Model _model = new Model();
private IDisposable _tempCleanUp = Disposable.Empty;
public ViewModel()
{
_cleanUp = new CompositeDisposable();
_collectionForCurrentBModel.Connect()
.Bind(out var aModelsForCurrentBModel)
.Subscribe(Console.WriteLine)
.DisposeWith(_cleanUp);
CollectionForCurrentBModel = aModelsForCurrentBModel;
this.WhenAnyValue(x => x.Model.Collection) // Every time Model in ViewModel changes:
.Subscribe(collection =>
{
// we dispose previous subscription:
_tempCleanUp.Dispose();
// then we manually reset SourceList<int> to match new collection:
_collectionForCurrentBModel.Edit(x =>
{
x.Clear();
x.AddRange(collection);
});
// finally, we manually subscribe to ObservableCollection<int>'s events to synchronize SourceList<int>.
_tempCleanUp = collection.ObserveCollectionChanges().Subscribe(pattern =>
{
switch (pattern.EventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
_collectionForCurrentBModel.AddRange(pattern.EventArgs.NewItems.Cast<int>());
break;
case NotifyCollectionChangedAction.Remove:
_collectionForCurrentBModel.RemoveRange(pattern.EventArgs.OldStartingIndex,
pattern.EventArgs.OldItems.Count);
break;
case NotifyCollectionChangedAction.Replace:
for (var i = 0; i < pattern.EventArgs.NewItems.Count; i++)
_collectionForCurrentBModel.Replace((int) pattern.EventArgs.OldItems[i],
(int) pattern.EventArgs.NewItems[i]);
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
break;
default:
throw new ArgumentOutOfRangeException();
}
});
});
}
public ReadOnlyObservableCollection<int> CollectionForCurrentBModel { get; }
public Model Model
{
get => _model;
set => this.RaiseAndSetIfChanged(ref _model, value);
}
public void Dispose()
{
_cleanUp.Dispose();
}
}
所以,ViewModel 有模型 属性。当前模型可以更改为另一个。 ViewModel 也有 CollectionForCurrentModel 属性,在这个例子中它基本上等于它的来源(Model.Collection)(但是,应该有一些排序,过滤等)。 CollectionForCurrentModel 属性 应该是只读的。 下面的代码按预期工作:
private static void Main(string[] args)
{
using var viewModel = new ViewModel();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(0);
// viewModel.Collection: {0}
viewModel.Model.Collection.Add(1);
// viewModel.Collection: {0, 1}
var oldModel = viewModel.Model;
viewModel.Model = new Model();
// viewModel.Collection: {}
viewModel.Model.Collection.Add(2);
// viewModel.Collection: {2}
oldModel.Collection.Add(3);
// viewModel.Collection: {2}
}
但是,在ViewModel中添加新的字段来存储最新的订阅,手动取消订阅和手动同步集合看起来很丑陋。还有其他订阅方式吗:
IObservable<IObservable<IChangeSet<T>>>
\ is result of
this.WhenAnyValue(x => x.ObservableCollection, selector: collection => collection.ToObservableChangeSet();
? DynamicData 能否自动管理内部订阅以将可变 属性 中的可观察集合绑定到其他集合?
这应该很容易。你可以这样做:
this.WhenAnyValue(x => x.Model.Collection)
.Select(collection => collection.ToObservableChangeSet())
.Switch() //this is the dynamic data overload of rx.Switch()
.Bind(out var aModelsForCurrentBModel)
.Subscribe();
select 语句 returns 可观察的更改集的可观察,它本身不是很有用。这就是为什么需要 switch 语句的原因。设置集合后,它会从缓存中清除现有项目并从新的可观察集合中加载项目。之后,您可以简单地绑定到目标可观察集合。
使用此技术意味着无需手动维护源列表。