A common occurrence in MVVM development is having calculated properties on your ViewModel. Calculated properties are often put on the ViewModel in order to avoid putting that logic in the UI code, as well as provide convenient databinding.
For example, let’s say you have a ViewModel property Cost which is a calculated property that depends on properties Quantity and Price. Setting values on Quantity and Price will raise a PropertyChanged
event for themselves, and also for Cost.
Traditionally, the ViewModel properties would look like this:
private int myQuantity;
public int Quantity
{
get { return myQuantity; }
set
{
myQuantity = value;
RaisePropertyChanged("Quantity");
RaisePropertyChanged("Cost");
}
}
private decimal myPrice;
public decimal Price
{
get { return myPrice; }
set
{
myPrice = value;
RaisePropertyChanged("Price");
RaisePropertyChanged("Cost");
}
}
public decimal Cost
{ get { return Quantity * Price; } }
The Pain
What bugs me about this approach is that Quantity and Price have to know about Cost (Quantity/Price -> Cost). In my opinion, that flow of knowledge should be reversed: only Cost should know that it depends on Quantity and Price (Cost -> Quantity/Price). Indeed, in Cost‘s getter we can clearly see that calculation already knows about Quantity and Price. So putting the notification logic in the same place results in no additional conceptual knowledge.
This follows the software engineering principles of increasing Cohesion (the degree to which the elements of a module belong together) and decreasing Coupling (specifically, Content Coupling, when one module modifies or relies on the internal workings of another module).
A Solution
So how can we keep that knowledge grouped properly? That’s why I wrote MvvmNotificationChainer – so the code can be rewritten like this:
private int myQuantity;
public int Quantity
{
get { return myQuantity; }
set
{
myQuantity = value;
RaisePropertyChanged("Quantity");
}
}
private decimal myPrice;
public decimal Price
{
get { return myPrice; }
set
{
myPrice = value;
RaisePropertyChanged("Price");
}
}
public decimal Cost
{
get
{
myNotificationChainManager.CreateOrGet()
.Configure (cn => cn.On (() => Quantity)
.On (() => Price)
.Finish ());
return Quantity * Price;
}
}
protected readonly NotificationChainManager myNotificationChainManager = new NotificationChainManager();
public MyViewModel ()
{
myNotificationChainManager.Observe (this);
myNotificationChainManager.AddDefaultCall ((sender, notifyingProperty, dependentProperty) => RaisePropertyChanged (dependentProperty));
}
Now, by using the MvvmNotificationChainer library, only Cost knows that it depends on Quantity and Price. Quantity and Price are completely independent and unaware of Cost.
Here’s the NotificationChainManager.CreateOrGet()
method:
/// <summary>
/// Creates a new NotificationChain for the calling property, or returns an existing instance
/// </summary>
/// <param name="dependentPropertyName">Name of the property that depends on other properties</param>
/// <returns></returns>
public NotificationChain CreateOrGet ([CallerMemberName] string dependentPropertyName = null)
{
dependentPropertyName.ThrowIfNull ("dependentPropertyName");
if (IsDisposed) return null;
NotificationChain chain;
if (!myChains.TryGetValue (dependentPropertyName, out chain))
{
chain = myChains[dependentPropertyName] = new NotificationChain (dependentPropertyName);
foreach (var callback in myDefaultCallbacks)
chain.AndCall (callback);
}
return chain;
}
The manager helps to provide, consolidate, and configure multiple NotificationChain
s for a notifying object. Note that this doesn’t have to be a ViewModel, it’s really for any class that implements or depends on INotifyPropertyChanged
(or PropertyChangedEventHandler
s).
[CallerMemberName]
is used so that there is compile-time property name safety w/o using Expression<Func<T>>
, and then you don’t have to specify the dependentPropertyName parameter.
The NotificationChain.On
methods to supply a lambda Expression to specify which changed properties to watch.
Each chain is smart enough to call Configure()
just once – therefore you can use Expression<Func<T>>
; to have compile-time property name safety but only pay the performance penalty for the initial call. After Finish()
is called, the other methods won’t do anything.
Usage
Now let’s analyze the method calls line by line
/*NotificationChainManager*/ .Observe (this);
The manager will observe the current class (that implements INotifyPropertyChanged
) and publish notification events to its chains.
/*NotificationChainManager*/ .AddDefaultCall ((sender, notifyingProperty, dependentProperty) => RaisePropertyChanged (dependentProperty));
When a new chain is created, and when a watched property changes, have it call the current class’ RaisePropertyChanged event.
/*NotificationChainManager*/ .CreateOrGet()
Creates or gets an existing chain for the calling dependent property
/*NotificationChain*/ .Configure (cn => cn // ...
Configures the chain with the given Action<NotificationChain>
, which executes unless Finish()
has been called.
/*NotificationChain*/ .On (() => Quantity)
Tells the chain to watch for Quantity to change
/*NotificationChain*/ .On (() => Price)
Tells the chain to watch for Price to change
/*NotificationChain*/ .Finish ()
Tells the chain that it is done being configured, and not to allow any further configuration changes.
Deep Observation
NotificationChain
can also observe “deep” – for example:
private User myUser;
public User User
{
get { return myUser; }
set
{
myUser = value;
RaisePropertyChanged("User");
}
}
public String UserFirstName
{
get
{
myNotificationChainManager.CreateOrGet()
.Configure (cn => cn.On (() => User, u => u.FirstName)
.Finish ());
return User != null ? User.FirstName : String.Empty;
}
}
UserFirstName will notify when User or User.FirstName notifies. Currently depth of 4 is supported, but it’s easy enough to add more depth if necessary.
Integrate with MVVM ICommands
NotificationChain
can also be used to support Commands – for instance, calling Prism’s DelegateCommand.RaiseCanExecuteChanged
when a watched property changes
// this is a notifying property that shouldn't have to know about DoSomethingCommand
private bool myHasInternetConnection;
public bool HasInternetConnection
{
get { return myHasInternetConnection; }
set
{
myHasInternetConnection = value;
RaisePropertyChanged ("HasInternetConnection");
}
}
public MyViewModel ()
{
DoSomethingCommand = new DelegateCommand (DoSomething, CanDoSomething);
// or you could use MvvmCommandWirer :)
}
public DelegateCommand DoSomethingCommand
{ get; private set; }
private bool CanDoSomething ()
{
// supply Expression since we're calling from a method
myNotificationChainManager.CreateOrGet (() => DoSomethingCommand)
.Configure (cn => cn.On (() => HasInternetConnection)
// clear default calls since we don't want to call RaisePropertyChanged
.AndClearCalls ()
.AndCall (DoSomethingCommand.RaiseCanExecuteChanged)
.Finish ());
return HasInternetConnection;
}
private void DoSomething ()
{
// does something
}
We can eliminate Content Coupling by keeping knowledge of DoSomethingCommand away from HasInternetConnection and making DoSomethingCommand code contain the relationship to HasInternetConnection (which it already does via the return value).
Summary
So with MvvmNotificationChainer, you can reduce coupling by keeping the flow of knowledge one way – from dependent to source properties – for both Properties and Commands.
Try out MvvmNotificationChainer on github!
Get MvvmNotificationChainer on NuGet!
Acknowledgements
Thanks to Interknowlogy’s PDFx for the inspiration! I might’ve gone with their library but it looked like I would’ve needed to rewrite my classes to implement another interface/base class.
Feedback Welcome
What do you think? Am I on target, or off my rocker? Can you suggest a better solution? I’m always looking for a better way!
Leave a comment or drop me a line on twitter!
Pingback: MVVM pain points | PhilChuang.com
Pingback: MVVM Notification Chainer nuget package updated to 2.0 | PhilChuang.com