我有一个WPF MVVM应用程序。我的模型对象是我完全可以控制的POCO。这些对象中的某些属性之间存在关系。
例如:假设我有
public class Model
{
public ObservableCollection<double> XCoordinates { get; set; } // Cumulative sum of DistancesBetweenXCoordinates.
public ObservableCollection<double> DistancesBetweenXCoordinates { get; set; } // {x[1]-x[0], x[2]-x[1], ..., x[n]-x[n-1]}
public double TotalLength { get; set; } // Sum of DistancesBetweenXCoordinates.
}
我希望最终用户能够编辑距离列表或x坐标列表,其他属性应该自动更新。到目前为止,我已经使用INPC事件完成了这项工作,但这很快就变得太乱了。一些UI更新在每个PropertyChange之后完成,所以我想优化它。
由于我的真实应用中的某些属性更新对性能影响不小,我不想使用“计算属性”,例如
public double TotalLength => DistancesBetweenXCoordinates.Sum();
根据我的阅读,ReactiveUI框架似乎具有我正在寻找的功能。我有一些问题:
1)我目前正在使用另一个框架(Catel),我宁愿完全抛弃它。我是否可以使用ReactiveUI中的.WhenAny()
等而不继承ReactiveObject
(例如仅通过实施IReactiveObject
)?
2)我见过的几乎所有例子都继承自ViewModel中的ReactiveObject
。这是实现我想要的首选方式吗?在我的模型中实现这个没有意义吗?如果我的模型应该只是一个“哑”的POCO而没有任何机制来保持所有相关属性的最新状态,那么这是我的Reactive VM的责任吗?
我会非常感谢一个简单的例子或其他指导。
答案 0 :(得分:8)
INotifyPropertyChanged
,您可以使用.WhenAny()
ReactiveObject
可用于ViewModel和Model类。它实际上只是一个反应性的&#34;宾语。即像System.Object
。 (不要相信我?看看the source。)ObservableAsPropertyHelper
用于依赖于其他反应属性或事件的计算属性。 (见docs)有几种方法可以解决这个问题,但特别是你似乎想要推理出几件事:
ReactiveObject
应仅限于ViewModels吗?它在普通的Model类中有用吗?首先,正如您已经注意到的那样,ReactiveUI的大部分内容都伴随着.WhenAny()
中包含的强大功能。你能在框架之间使用这些方法,是! .WhenAny()
,.WhenAnyValue()
,.WhenAnyObservable()
方法适用于实现INotifyPropertyChanged
的任何对象。 (Related Docs)
事实上,您现有的框架可能已经在许多自己的类型上实现了INotifyPropertyChanged
,因此.WhenAny()
自然地扩展到无缝地处理这些对象。您几乎从未实际需要 ReactiveObject
。它只会让你的生活更轻松。
注意:这实际上是ReactiveUI的核心价值之一。 ReactiveUI的核心是really just a bunch of extension methods,旨在使现有的.Net世界更容易使用observable。这使得与现有代码的互操作性成为ReactiveUI最引人注目的功能之一。
现在,ReactiveObject
应该用于正常&#34; dumb&#34;楷模?我想这取决于你想要承担责任的地方。如果你希望你的模型类只包含规范化状态而根本没有逻辑,那么可能不是。但是,如果您的模型旨在处理状态和域相关的逻辑,那么为什么不呢?
注意:这里有关于single responsibility principal,软件架构和MVVM的更大的哲学辩论,但这可能是Programmers SE。< / p>
在这种情况下,我们关心通知侦听器有关某些计算属性(如TotalLength
)的更新。 (即我们的模型包含一些逻辑)ReactiveObject
帮助我们这样做吗?我想是的。
在您的方案中,我们希望只要添加或更改元素或其他内容,就可以从TotalLength
计算DistancesBetweenXCoordinates
。我们可以使用ReactiveObject
和ObservableAsPropertyHelper
的组合。 (Related Docs)
例如:
class Model : ReactiveObject
{
// Other Properties Here...
// ObservableAsPropertyHelper makes it easy to map
// an observable sequence to a normal property.
public double TotalLength => totalLength.Value;
readonly ObservableAsPropertyHelper<double> totalLength;
public Model()
{
// Create an observable that resolves whenever a distance changes or
// gets added.
// You would probably need CreateObservable()
// to use Observable.FromEventPattern to convert the
// CollectionChanged event to an observable.
var distanceChanged = CreateObservable();
// Every time that a distance is changed:
totalLength = distanceChanged
// 1. Recalculate the new length.
.Select(distances => CalculateTotalLength(distances))
// 2. Save it to the totalLength property helper.
// 3. Send a PropertyChanged event for the TotalLength property.
.ToProperty(this, x => x.TotalLength);
}
}
在上文中,每次TotalLength
观察结果解析时都会重新计算distanceChanged
。例如,只要DistanceBetweenXCoordinates
发出CollectionChanged
事件,就可以这样做。此外,因为这只是一个普通的可观察对象,您可以在后台线程上进行计算,从而允许您在长时间操作期间保持UI响应。计算完成后,会为PropertyChanged
发送TotalLength
事件。
答案 1 :(得分:0)
您也可以使用ReactiveList而不是ObservableCollection,例如:
public class Model : ReactiveObject
{
private ReactiveList<double> distancesBetweenXCoordinates;
private readonly ObservableAsPropertyHelper<double> totalLength;
public Model()
{
// ChangeTrackingEnabled allow to raise changes notifications when individual values changes
DistancesBetweenXCoordinates = new ReactiveList<double> { ChangeTrackingEnabled = true };
this.WhenAnyValue(x => x.DistancesBetweenXCoordinates, x => x.Sum())
.ToProperty(this, x => x.TotalLength, out totalLength);
}
public ReactiveList<double> DistancesBetweenXCoordinates
{
get => distancesBetweenXCoordinates;
set => this.RaiseAndSetIfChanged(ref distancesBetweenXCoordinates, value);
}
public double TotalLength { get => totalLength.Value; }
}
这样,对DistancesBetweenXCoordinates的各个元素的更改将改变TotalLengthvalue。