Mike Hillberg has some great observations about WPF application architecture as it pertains to model interaction in his “Model See Model Do” post. I am very much in agreement with Mike on this subject.
And let me also say that I’m very happy to see Mike posting a bit more on WPF recently. 🙂
There is another thing definitely worth highlighting in Mike’s latest post… In his code sample, he uses a little trick to ensure that bindings on his command and argument objects resolve correctly. Namely, he derives them from Freezable:
public class MethodArgument
: Freezable // Enable ElementName and DataContext bindings
Normally, ElementName and DataContext bindings are resolved based on the target dependency object’s position within the element tree (or the namescope to which the target dependency object belongs). But in this case, the target dependency object is not actually in the tree. Instead, it is just a property value on another object. That other object may or may not be in the tree.
The reason the Freezable trick works is because a Freezable object has its own notion of “inheritance context”. When the property engine sets the effective value of a dependency property, it looks at that new value to determine whether it is a dependency object that would like to be part of a special inheritance tree. A Freezable is one such object that always wants to be in the inheritance tree when not frozen. As such, when the Command property on Button is set to a Freezable in Mike’s example (below), the framework adds the Button itself as the inheritance context of the Freezable.
<Button Content="Rename"> <Button.Command> <mc:MethodCommand MethodName="Rename"> <mc:MethodArgument Value="{Binding Text, ElementName=_renameTextBox}" /> </mc:MethodCommand> </Button.Command> </Button>
The MethodCommand object above will now have the Button as its inheritance context. As such, even though MethodCommand is not a FrameworkElement with a tree-inherited DataContext property, it still effectively inherits the data context of the Button. A binding on any property of the MethodCommand object will use its inheritance context to arrive at an implicit binding source (which will be the DataContext of the Button in this case).
The same construct is used to pass the inheritance context from a MethodCommand to its MethodArgument objects, so that their bound properties, too, can be resolved.
(Note that a Freezable can also have more than one inheritance context, but I’ll leave the discussion of how multiple inheritance contexts are handled for a future post.)
In addition to the above scenario (where a freezable is set as a dependency property value on a dependency object), it is this enhanced notion of an inheritance tree and inheritance context that allows bindings on brushes, animations, and other freezable objects to work when those objects are placed within a resource dictionary.
Mike’s approach is definitely a cool trick and I’ve used it myself on occasion. Of course, there are scenarios where inheriting from Freezable is not really an option. In such situations, the hack I find most useful for enabling bindings on non-freezable objects is to artificially add such objects to the logical tree. But I would love to see Microsoft publicly expose the ability to control an object’s inheritance context so that all these hacks could just go away. (Hint, hint! ;))
Thank you so muxh for sharing this… It’s so cool!
Excellent post. I really need to use commands more. This approach seems really interesting.
It reminds me of my "virtual branch" technique, only your/Mike’s approach is much more terse.
Josh
I get the following warning in the Visual Studio output:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element…blah blah blah
The binding does work during runtime, but this shows up regardless. Any ideas?
Hi Tom,
WPF spits out debug information for bindings that cannot be resolved on the first attempt. Sometimes these messages can be confusing because the bindings later resolve when the tree is fully connected.
In this particular case, that is what you are seeing. It is safe to ignore this error message (which will only be generated if there is a debugger attached to the process).
Cheers,
-dw
Here’s a post which takes this concept further to allow Binding on CLR properties and elements outside an element tree.