ReactiveUI - 具有许多相关属性的模型对象

时间:2017-08-31 22:14:49

标签: c# mvvm system.reactive reactive-programming reactiveui

我有一个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的责任吗?

我会非常感谢一个简单的例子或其他指导。

2 个答案:

答案 0 :(得分:8)

TL;博士

  • 实施INotifyPropertyChanged,您可以使用.WhenAny()
  • ReactiveObject可用于ViewModel和Model类。它实际上只是一个反应性的&#34;宾语。即像System.Object。 (不要相信我?看看the source。)
  • ObservableAsPropertyHelper用于依赖于其他反应属性或事件的计算属性。 (见docs

答案

有几种方法可以解决这个问题,但特别是你似乎想要推理出几件事:

  1. 如果不从我正在使用的其他框架迁移,我如何利用反应功能?
  2. ReactiveObject应仅限于ViewModels吗?它在普通的Model类中有用吗?
  3. 首先,正如您已经注意到的那样,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。我们可以使用ReactiveObjectObservableAsPropertyHelper的组合。 (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。