I have a WPF MVVM application. My model objects are POCOs that I have full control over. Some properties in these objects have a relationship between them.
For example: let's say that I have
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.
}
I'd like the enduser to be able to edit either the list of distances, or the list of x-coordinates, and the other properties should automatically update. So far I've done this using INPC events, but this quickly gets too messy. Some UI updates are done after each PropertyChange, so I want to optimize this.
Since some property updates in my real app doesn't have negligible performance impact, I don't want to use "calculated properties" such as
public double TotalLength => DistancesBetweenXCoordinates.Sum();
From what I've read, the ReactiveUI framework seems to have the capabilities I'm looking for. I have some questions though:
1) I'm currently using another framework (Catel) that I rather not abandon completely. Can I use the .WhenAny()
etc. from ReactiveUI without inheriting from ReactiveObject
(for instance just by implementing IReactiveObject
)?
2) Almost all examples I've seen inherits from ReactiveObject
in the ViewModel. Is this the preferred way to achieve what I want? Would it not make more sense to implement this inside my model? If my Model should just be a "dumb" POCO without any mechanism to keep all related properties up-to-date, then is this the responsibility of my Reactive VM?
I would be really grateful for a simple example or some other guidance.
INotifyPropertyChanged
and you can use .WhenAny()
ReactiveObject
can be used for both ViewModel and Model classes. It is literally just a "reactive" object. i.e. like System.Object
. (Don't believe me? Check out the source.)ObservableAsPropertyHelper
for calculated properties that are dependent on other reactive properties or events. (See the docs)There are a couple of ways you could handle this, but in particular it seems like you want to reason out a couple things:
ReactiveObject
just be limited to ViewModels? Is it useful in normal Model classes?First as you've already noticed, a lot of the draw of ReactiveUI comes with the powerful capabilities contained in .WhenAny()
. Can you use these methods between frameworks, yes! The .WhenAny()
, .WhenAnyValue()
, .WhenAnyObservable()
methods work on any object that implements INotifyPropertyChanged
. (Related Docs)
In fact, it's likely that your existing framework already implements INotifyPropertyChanged
on many of their own types, so .WhenAny()
naturally extends to work on those objects seamlessly. You almost never actually need a ReactiveObject
. It just makes your life easier.
Note: This is actually one of the core values of ReactiveUI. That at the core, ReactiveUI is really just a bunch of extension methods designed to make working with observables easier in the existing .Net world. This makes interoperability with existing code one of ReactiveUI's most compelling features.
Now, should ReactiveObject
be used for normal "dumb" models? I guess it depends on where you want the responsibilities to lie. If you want your model class to only contain normalized state and no logic at all, then probably not. But if your model is designed to handle both state and domain-related logic, then why not?
Note: There's a larger philosophical debate here about the single responsibility principal, software architecture, and MVVM, but that's probably for Programmers SE.
In this instance, we care about notifying listeners about updates to some calculated properties such as TotalLength
. (i.e. our model contains some logic) Does ReactiveObject
help us do this? I think so.
In your scenario, we want TotalLength
to be computed from DistancesBetweenXCoordinates
whenever an element is added or changed or something. We can use a combination of ReactiveObject
and ObservableAsPropertyHelper
. (Related Docs)
For example:
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);
}
}
In the above, TotalLength
would be recalculated every time that the distanceChanged
observable resolves. This could for example be whenever DistanceBetweenXCoordinates
emits a CollectionChanged
event. In addition, because this is just a normal observable you could have the calculation occur on a background thread, allowing you to keep the UI responsive during a long operation. Once the calculation is done, the PropertyChanged
event is sent for TotalLength
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With