ReactiveUI MVVM框架(1)-Collections
ReactiveUI使用动态数据(DynamicData
)用于集合的操作。
当对动态数据集合进行更改时,会产生更改通知,通知表示为ChangeSet
,里面包含了更改信息,多个更改通知为IObservable<ChangeSet>
。动态数据提供了两种特定的集合,分别是SourceCache<TObject, TKey>
和SourceList<T>
。第一个为带key的字典式,也就是不能重复,第二个为集合式。如何要将这两种集合转换为IObservable<ChangeSet>
,可以使用Connect方法。
需要注意的是,这跟WPF中常用的ObservableCollection<T>
的实现方式是不同的。
简单使用
从wpf中常用的ObservableCollection<T>
得到IObservable<ChangeSet>
。
// 'myList' is ObservableCollection<T>
// 'myDerivedList' is IObservableList<T>
var myDerivedList = myList
.ToObservableChangeSet()
.Filter(t => t.Status == "Something")
.AsObservableList();
// 'myList' is ObservableCollection<T>
// 'myDerivedCache' is IObservableCache<T, TKey>
var myDerivedCache = myList
.ToObservableChangeSet(t => t.Id)
.Filter(t => t.Status == "Something")
.AsObservableCache();
以上两种方式是线程不安全的,加入myList绑定到了View上,那么在View上也可能对myList进行更改。推荐的方法是先创建一个数据源。
var myList = new SourceList<T>()
var disposable = myList
.Connect() // 获得IObservable<ChangeSet>
.\\some other operation
这种方法的好处是可以在后台线程上进行维护。比如:
//ReadOnlyObservableCollection可以多线程操作
ReadOnlyObservableCollection<T> bindingData;
var disposable = mySource
.Connect() // make the source an observable change set
.Sort(SortExpressionComparer<T>.Ascending(t => t.DateTime))
.ObserveOn(RxApp.MainThreadScheduler)
// 'mySource' 会在其他线程上更新
.Bind(out bindingData)
.Subscribe();
ReactiveUI使用动态数据
开发时,会遇到可变集合和不可变集合,当对不可变集合进行处理时,简单情况下可以使用ObservableAsPropertyHelper<T>
,它包含一个Observable<T>
。每次给集合赋予新的集合时会触发通知事件。
而对于可变集合,往往采用动态数据的方式。
案例
public class Service
{
//定义一个数据集
private readonly SourceList<bool> _items = new SourceList<bool>();
//暴露给外面
public IObservable<IChangeSet<bool>> Connect() => _items.Connect();
public Service()
{
_items.Add(true);
_items.RemoveAt(0);
_items.Add(false);
}
}
ReadOnlyObservableCollection
- 动态数据往往使用
ReadOnlyObservableCollection<T>
之类的类型对外公开,而不是它本身的类型。IObservable<IChangeSet<T>>
和IObservable<IChangeSet<TObject, TKey>>
是可以观测类型,IObservable<IChangeSet<T>>
中含有集合更改的内容,第一次使用ToObservableChangeSet()
时会发出集合的当前状态。 SourceList
和SourceCache
是可以使用多线程进行创建IObservable<IChangeSet<T>>
等,通常SourceList
和SourceCache
应该定义为Private,而是通过Connect方法暴露给View。
public class ViewModel : ReactiveObject
{
private readonly ReadOnlyObservableCollection<bool> _items;
public ReadOnlyObservableCollection<bool> Items => _items;
public ViewModel()
{
var service = new Service();
service.Connect()
// Transform 和Select方法类似,只不过是观察一个集合的变化且将元素投影到另一个集合
.Transform(x => !x)
// Filter 类似于Where
.Filter(x => x)
// 确保更先到UI线程.
.ObserveOn(RxApp.MainThreadScheduler)
// 通过 .Bind() 方法实现可变集合包含新的数据并且刷新UI
.Bind(out _items)
.Subscribe();
}
}
ObservableCollectionExtended
ObservableCollectionExtended<T>
是一个单线程集合,如果要同步VM中的两个集合,可以将其中一个声明为ObservableCollectionExtended<T>
,另一个声明为ReadOnlyObservableCollection<T>
,然后使用.ToObservableChangeSet()
方法将其转换为IObservable<IChangeSet<T>>
。
public class SynchronizedCollectionsViewModel : ReactiveObject
{
private readonly ReadOnlyObservableCollection<bool> _derived;
public ReadOnlyObservableCollection<bool> Derived => _derived;
public ObservableCollectionExtended<bool> Source { get; }
public SynchronizedCollectionsViewModel()
{
Source = new ObservableCollectionExtended<bool>();
Source.ToObservableChangeSet()
.Transform(value => !value)
// 在这里不需要使用ObserveOn更新UI线程,因为它是单线程
.Bind(out _derived)
.Subscribe();
Source.Add(true);
Source.RemoveAt(0);
Source.Add(false);
Source.Add(true);
}
}
根据集合中的更改
ReactiveObject
类实现了INotifyPropertyChanged
,动态数据可以对ReactiveObject
类进行跟踪。
// 'collectionOfReactiveObjects' 是 ObservableCollection<T>
// T inherits 继承自 ReactiveObject
// 'databasesValid' 则是 IObservable<bool>
var databasesValid = collectionOfReactiveObjects
.ToObservableChangeSet()
.AutoRefresh(model => model.IsValid) // 订阅IsValid属性的更改
.ToCollection() // 获取新项目集合
.Select(x => x.All(y => y.IsValid)); // 验证是否满足条件.
// 将IObservable<bool> 转为视图模型
// '_databasesValid' 是ObservableAsPropertyHelper<bool> 类型
_databasesValid = databasesValid.ToProperty(this, x => x.DatabasesValid);
ReactiveList转为动态数据
如果使用的是ReactiveList<T>
,并且仅从UI线程添加/删除,则使用ObservableCollectionExtended<T>
。