Introduction
This article demonstrates how to create a "virtual branch" of the logical tree in a WPF user interface. By "virtual branch" I mean a set of elements which is not physically in a logical tree, but can make use of the DataContext
which propagates down. Elements in a virtual branch are not recognized by the LogicalTreeHelper
, nor do they participate in the routed event and command systems. The impetus for attaching a virtual branch to the logical tree is that it allows for data binding scenarios that are otherwise impossible.
Instead of just diving straight into the code and markup which creates a virtual branch, we will first review a problem that can be solved by using one. Once we have ensconced the rather abstruse notion of "virtual branches" in a more approachable context we will then review what general problem they solve, and the details of their implementation.
The problem
Suppose that we create a simple application which allows us to select a number on a Slider
, and then enter a multiple of that number into a TextBox
. The application should provide a visual cue when the number in the TextBox
is not a multiple of the number selected on the Slider
. Perhaps the user interface might look like this:
The two numbers entered by the user are stored in an instance of a simple class named DivisionNumbers
, which implements this interface:
interface IDivisionNumbers : INotifyPropertyChanged
{
int Dividend { get; set; }
int Divisor { get; set; }
}
In the Window
's constructor we configure an instance of DivisionNumbers
and set it as the Window
's DataContext
so that all elements in the application can bind to it, like so:
public Window1()
{
InitializeComponent();
DivisionNumbers nums = new DivisionNumbers();
nums.Divisor = 1;
nums.Dividend = 1;
this.DataContext = nums;
}
The logic which validates the number in the TextBox
was put into a ValidationRule
subclass. An instance of that class is added to the ValidationRules
property of the Binding
associated with the TextBox
's Text
property. When the user types a number into the TextBox
our ValidationRule
checks to see if that number is valid. This XAML looks something similar to:
<TextBox x:Name="dividendTextBox">
<TextBox.Text>
<Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IsMultipleOfValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
An incomplete version of our custom ValidationRule
is implemented like this:
public class IsMultipleOfValidationRule : ValidationRule
{
public override ValidationResult Validate(
object value, CultureInfo cultureInfo )
{
try
{
int dividend = (int)Convert.ChangeType( value, typeof( int ) );
int divisor =
if( dividend % divisor == 0 )
return ValidationResult.ValidResult;
return new ValidationResult(
false,
"The number is not a multiple of " + divisor );
}
catch
{
return new ValidationResult( false, "Not a number." );
}
}
}
A problem now reveals itself. How can we get the value of the divisor selected by the user from within the Validate
method?
We cannot add an integer property called Divisor
to IsMultipleOfValidationRule
and bind to it, because only dependency properties can be bound. We cannot add a dependency property to the class either because they can only exist in classes which derive from DependencyObject
. ValidationRule
does not derive from that class.
Even if we did somehow add an integer dependency property named Divisor
to IsMultipleOfValidationRule
it would not help. The ValidationRule
objects owned by a Binding
are not added to the logical tree. Since DataContext
is inherited only by elements in the logical tree, our custom ValidationRule
would have no way to access that value. To top it all off, ValidationRule
does not even have a DataContext
property since it does not derive from FrameworkElement
!
Similarly, ValidationRule
s are never added to a namescope. Since the ValidationRule
is not in a namescope you would not be able to bind to the Slider
via the ElementName
property of Binding
. The lookup process used to resolve ElementName
to an element requires that the Binding
is tied to an instance of a DependencyObject
subclass which was added to the same namescope as the target element.
How can we elegantly overcome this seemingly impossible technical barrier to achieve our modest goal?
The solution
Attaching a virtual branch to a logical tree can provide objects that are not in the tree with data they need from it. Conceptually speaking, attaching a virtual branch to a logical tree is akin to tapping your neighbor's telephone line so that you can listen to their phone conversations about topics which interest you. We will, instead, tap into a user interface's logical tree and make use of the DataContext
value which propagates down. The only "wire splicing" involved with our technique is to add a touch of XAML to a certain element in the logical tree.
Simply put, elements in a virtual branch can bind against the DataContext
of the logical tree to which the branch is attached. It is possible to attach a virtual branch to any element in the logical tree, in case certain subsections of the tree are assigned a different DataContext
to bind against. You can apply more than one virtual branch to the same logical tree, if necessary.
Without getting into implementation details just yet, let's review what a virtual branch is and what it is not. The next section in this article shows how to implement the pattern.
The term "virtual branch" is neither an official WPF term nor is it represented by any programmatic construct (i.e. there is no VirtualBranch
class). It is a term we can use to refer to a reusable pattern which I designed to overcome the aforementioned technical limitations imposed by the WPF data binding model.
What I refer to as a "physical branch" is a hierarchical grouping of elements which actually exists in a logical tree. Elements in a physical branch are returned by methods of LogicalTreeHelper
. Elements in a virtual branch are not. Elements in a physical branch participate in the routed event and command systems. Elements in a virtual branch do not. Elements in a virtual branch are not actually in the logical tree to which the branch is attached.
Implementing a virtual branch
It is very easy to create a virtual branch. There are only three pieces to the puzzle. For a visual explanation of the technique we are about to examine, refer to the diagram below:
Step 1 � Go build a bridge
Add a FrameworkElement
to the Resources
collection of the element to which you want to attach the virtual branch. This element acts a bridge between the logical tree and the virtual branch. The logical tree's DataContext
is pushed across the bridge via data bindings. The bridge element becomes the root node of the virtual branch. In this article's demo application, the bridge element is added to the Window
's Resources
, as seen below:
<Window.Resources>
-->
<FrameworkElement x:Key="DataContextBridge" />
</Window.Resources>
Step 2 � Push the DataContext across the bridge
At this point we have an element in place ready to expose the logical tree's DataContext
to elements in the virtual branch. Now we need to make sure that the bridge element's DataContext
always has the same value as one particular element in the logical tree. We will accomplish this by putting the rarely used 'OneWayToSource
' binding mode to use, as seen below:
<Window.DataContext>
-->
<Binding
Mode="OneWayToSource"
Path="DataContext"
Source="{StaticResource DataContextBridge}"
/>
</Window.DataContext>
The Binding
seen above ensures that whenever the Window
's DataContext
property is set, the new value will be pushed into the bridge element's DataContext
as well. Ideally the Window
would not need to have this Binding
applied to its DataContext
. It would be cleaner if the bridge element could contain that Binding
(for the sake of encapsulation). However it is necessary to establish this Binding
on the Window
's DataContext
because the bridge element is a resource living in a ResourceDictionary
, and thus cannot bind to elements in the element tree.
It is important to note that the Binding
seen above must be applied to the element in the logical tree to which you actually assign the DataContext
property a value, as opposed to some other element which just happens to inherit that value. In the demo application the DataContext
is set on the Window
, so the Binding
is applied to the Window
's DataContext
property.
Step 3 � Pull the DataContext off the bridge
The last step is to make use of the DataContext
which was smuggled over to the virtual branch. Here is our opportunity to finally get the value of the divisor from within the IsMultipleOfValidationRule
, as discussed earlier. This step can be broken into three tasks:
Step 3a � Create a data container class
All nodes in a virtual branch should derive from FrameworkElement
, even if they are only meant to hold a simple value. Deriving from FrameworkElement
gives the object a DataContext
dependency property, and enables us to create our own dependency properties (FrameworkElement
derives from DependencyObject
).
Below is the class which will hold the divisor to be used by our validation rule:
public class IntegerContainer : FrameworkElement
{
public int Value
{
get { return (int)GetValue( ValueProperty ); }
set { SetValue( ValueProperty, value ); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof( int ),
typeof( IntegerContainer ),
new UIPropertyMetadata( 0 ) );
}
Step 3b � Use the data container
Now it is time to put IntegerContainer
to use in the IsMultipleOfValidationRule
class. We will expose an instance of that class as a public
read-write property. Here is the complete version of our validation rule:
public class IsMultipleOfValidationRule : ValidationRule
{
private IntegerContainer divisorContainer;
public IntegerContainer DivisorContainer
{
get { return divisorContainer; }
set { divisorContainer = value; }
}
public override ValidationResult Validate(
object value, CultureInfo cultureInfo )
{
try
{
int dividend = (int)Convert.ChangeType( value, typeof( int ) );
int divisor = this.DivisorContainer.Value;
if( dividend % divisor == 0 )
return ValidationResult.ValidResult;
return new ValidationResult(
false,
"The number is not a multiple of " + divisor );
}
catch
{
return new ValidationResult( false, "Not a number." );
}
}
}
Step 3c � Bind to the bridge
The final task is to bind the IntegerContainer
's DataContext
to the bridge element's DataContext
. Once the IntegerContainer
's DataContext
is bound it will reference the same DivisionNumbers
object as all of the elements in the logical tree. At that point we can bind to its Divisor
property with ease. The following XAML configures the TextBox
and our custom validation rule:
<TextBox x:Name="dividendTextBox">
<TextBox.Text>
<Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IsMultipleOfValidationRule>
<local:IsMultipleOfValidationRule.DivisorContainer>
-->
<local:IntegerContainer
DataContext="{Binding
Source={StaticResource DataContextBridge},
Path=DataContext}"
Value="{Binding Divisor}"
/>
</local:IsMultipleOfValidationRule.DivisorContainer>
</local:IsMultipleOfValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Parting words
I believe that this pattern will be put to use in ways I never imagined. There are most likely ways to use virtual branches that are detrimental or destabilizing to an application, so be careful with them. If you use a virtual branch to solve a problem unlike the one shown here, please leave a comment on this article's message board explaining the situation.
History
- May 6, 2007 - Created article