-
-
Notifications
You must be signed in to change notification settings - Fork 16
Alternatives
Manual implementation is a possibility, though not a great one. The code can use expression lambdas to look like this:
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
OnPropertyChanged(() => Greeting);
}
}
public string Greeting
{
get { return "Hello, " + Name + "!"; }
}
However, this is not maintainable. The core problem is that the calculation of MyCalculatedValue
and the declaration of the dependency are far apart, in different properties. As the calculations change and grow more complex, eventually the dependencies will not be correct.
You can also define a property dependency mapping dictionary in code, as this StackOverflow answer suggests. A concrete implementation is available in a Code Project article. This solution makes the properties look like:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
[DependentOn("Name")]
public string Greeting
{
get { return "Hello, " + Name + "!"; }
}
This solution moves the dependency declaration near the actual dependency, so it's a great step in the right direction. Unfortunately, lambdas cannot be used in attributes, so this solution is forced to use magic strings for all the dependency properties. Also, it's not possible to have a dependency across different ViewModel instances; a parent or child ViewModel cannot depend on the other's properties. It would be possible but difficult to extend the attribute solution to support property paths in addition to simple property names.
RxUI has support for declaring dependencies in a slightly roundabout way: it can treat PropertyChanged
events as observables, and use observables to drive calculated properties (including PropertyChanged
). The RxUI code looks like this:
public sealed class RxUiViewModel : ReactiveObject
{
private string _name;
public string Name
{
get { return _name; }
set { this.RaiseAndSetIfChanged(ref _name, value); }
}
private readonly ObservableAsPropertyHelper<string> _greeting;
public string Greeting
{
get { return _greeting.Value; }
}
public RxUiViewModel()
{
_greeting = this.ObservableForProperty(vm => vm.Name, skipInitial: false)
.Select(x => "Hello, " + x.Value + "!")
.ToProperty(this, vm => vm.Greeting);
}
}
This example derives from ReactiveObject
, but there are ways to use your own VM base class.
This approach is pretty good; it declares the dependencies close to the calculation delegate, and avoids magic strings. It does force the dependencies and calcualtions to be defined in the constructor, which can be a bit unweildly, and Rx has a learning curve most developers haven't crossed yet. But I do like what RxUI tries to do.
After I published this library, my friend Phil Chuang pointed out that he had written something similar: MvvmNotificationChainer.
His library uses more fluent method names:
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Greeting
{
get
{
_chainManager.CreateOrGet().Configure(cn =>
cn.On(() => Name)
.Finish());
return "Hello, " + Name + "!";
}
}
private readonly NotificationChainManager _chainManager = new NotificationChainManager();
public MvvmNotificationChainerViewModel()
{
_chainManager.Observe(this);
_chainManager.AddDefaultCall((sender, notifyingProperty, dependentProperty) =>
OnPropertyChanged(dependentProperty));
}
The Configure
logic is skipped after Finish
is invoked, so the actual defining of the dependencies is only done on the first call. I haven't played with this yet, but it looks like it follows some of the same goals as CalculatedProperties: namely, it can work with any framework, and the calculation (with dependencies) is defined in the actual property getter. The main differences appear to be that MvvmNotificationChainer does work by reacting to PropertyChanged
notifications, and that CalculatedProperties detects dependencies automatically; there are advantages and disadvantages both ways.
MvvmNotificationChainer uses a similar conceptual structure and fluent syntax as PDFx - which apparently does not have a NuGet package?
A more exhaustive solution is UpdateControls. The problem with UpdateControls is that each ViewModel is all-or-nothing; if your type implements INotifyPropertyChanged
then you have to raise it yourself for all your properties. Also, UpdateControls will wrap your actual ViewModel within a data-bindable wrapper that is actually bound to the View. Finally, UpdateControls tries too hard to be a comprehensive MVVM framework; I just want a little library that can work with any framework, and allow me to gradually transition.