Flyweight M-V-VM: Decorator revision

Revision: Behavior problems

In my original post I provided a behavior (ViewModelBehavior) for provding a view model for the given DataContext.  The behavior saved away the original DataContext as the model, set DataContext of the AssociatedObject to the view model,  and passed the model to the view model.

This worked in 90% of the basic data binding scenarios.  However, it fails to work properly when the DataContext contains a relative data binding expression.  The root source of the problem is that the behavior is a child of the AssociatedObject (from a data binding perspective).

Changing the DataContext causes problems when the original DataContext binding expression is relative.  When the behavior saves the original DataContext to a dependency property it owns, two things break:

First, the relative position of the DataContext on the behavior is one level deeper than the DataContext at the AssociatedObject level.  This means any relative binding will be starting from the wrong point in the tree and won’t get the right value.

Second, when the AssociateObject’s DataContext is set to the view model, this affects the binding evaluation from the original DataContext.  This corrupts the saved DataContext value and the view model never gets the model.

Attempt: Attached property

To solve the first problem, I attempted to use an attached property on the AssociatedObject to make a copy of the DataContext.  This would mean the data binding statement of the copy would start at the same point as the original DataContext.

I had to write some ugly code to clone the binding and to set the initial value of the attached property to force an attachment at run-time.  Unfortunately, the attached property was still affected by the change to the DataContext.  This is because WPF follows the ordering of attributes.  I attempted to clear the DataContext, set the original DataContext, and then re-apply the DataContext. This didn’t work either as WPF has some special affinity for the DataContext property and always gives it priority.

Attempt: Cloned Binding

To try and solve the second problem, I did a whole bunch of ugly code for making a smart clone of the data binding.  However, the inherited nature of the DataContext made this approach unworkable. The copy couldn’t participate in the same inheritance chain as the DataContext property.

Analogy: Variable swap

After the failed attempts to solve the problem, I realized it was like  trying to swap two variables without having a temp variable.  A good interview question, but not a great WPF behavior.  By adding another control (like a Grid, Border, etc), I could add another level and separate the DataContext binding expression away from the AssociatedObject control.  This provides the temp variable for making the swap.

I considered not allowing (or restricting) the DataBinding expression on the AssociatedObject.  This isn’t a good choice though because it would interfere with other behaviors, prevent normal XAML development, and favors a run-time exception for a design-time error.

Solution: Decorator

Given that I need to ensure a level in the XAML where the DataContext could be changed, I decided to use a Decorator as a base class rather than a behavior.  The decorator could have it’s DataContext set and could modify the DataContext of its only child.  I thought this would make more sense to the developer than the behavior; everything within the decorator would have the view model as the DataContext.

The improved simplicity of understanding the data binding scope comes as the cost that the developer has to add a new XAML element.  This sacrifices on of my goals for  M-V-VM: “The library does not require to change how you build your XAML hierarchy”.  I thought a lot about these tradeoffs and after using it for a bit, the decorator approach seems worth it.

The ViewModelScope class inherits from Decorator.  It has the same ViewModelType and static ViewModelFactory properties as the behavior.  To use it, you just add it to your XAML like you would a Border:

<bellacode:ViewModelScope ViewModelType="{x:Type local:MyViewModel}" >
    ...
</bellacode:ViewModelScope>

Because ViewModelScope is a Decorator which derives from FrameworkElement, you can set the DataContext just like any other FrameworkElement.

I’ve included the updated Flyweight M-V-VM library here.  The HelloWorld sample application is updated and it includes the POCO library from commands from my previous post.



Leave a Reply