Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF
Article

Attaching a Virtual Branch to the Logical Tree in WPF

Rate me:
Please Sign up or sign in to vote.
5.00/5 (33 votes)
6 May 2007CPOL8 min read 251.7K   1.2K   62   59
Reviews a pattern which enables new possibilities for data binding.

Image 1

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:

Image 2

The two numbers entered by the user are stored in an instance of a simple class named DivisionNumbers, which implements this interface:

C#
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:

C#
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:

XML
<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:

C#
public class IsMultipleOfValidationRule : ValidationRule
{
 public override ValidationResult Validate(
    object value, CultureInfo cultureInfo )
 {
  try
  {
   int dividend = (int)Convert.ChangeType( value, typeof( int ) );

   int divisor = // Get the divisor somehow...

   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, ValidationRules 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:

Image 3

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:

XML
<Window.Resources>
  <!-- This is the "root node" in the virtual branch
       attached to the logical tree. It has its
       DataContext set by the Binding applied to the
       Window's DataContext property. -->
  <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:

XML
<Window.DataContext>
  <!-- This Binding sets the DataContext on the "root node"
       of the virtual logical tree branch.  This Binding
       must be applied to the DataContext of the element
       which is actually assigned the data context value. -->
  <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:

C#
/// <summary>
/// Stores an integer in the Value property.  Derives from
/// FrameworkElement so that it gets the DataContext property.
/// </summary>
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:

C#
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:

XML
<TextBox x:Name="dividendTextBox">
  <TextBox.Text>
    <Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <local:IsMultipleOfValidationRule>
          <local:IsMultipleOfValidationRule.DivisorContainer>
            <!-- This IntegerContainer is the "child node" of
                 the DataContextBridge element, in the virtual
                 branch attached to the Window's logical tree. -->
            <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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Josh creates software, for iOS and Windows.

He works at Black Pixel as a Senior Developer.

Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.

Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.

Check out his Advanced MVVM[^] book.

Visit his WPF blog[^] or stop by his iOS blog[^].

See his website Josh Smith Digital[^].

Comments and Discussions

 
QuestionCode for two text boxes instead of slider and text box Pin
Member 133443423-Aug-17 14:44
Member 133443423-Aug-17 14:44 
QuestionBinding non dependency properties Pin
mohsenbz3-Sep-13 18:58
mohsenbz3-Sep-13 18:58 
QuestionException thrown by XAML code Pin
aloifolia26-Jul-12 4:23
aloifolia26-Jul-12 4:23 
SuggestionIncredible shortcut if you're using MVVM Light or already have DataContext available in XAML.. Look within Pin
grahamoneale16-May-12 14:34
grahamoneale16-May-12 14:34 
GeneralRe: Incredible shortcut if you're using MVVM Light or already have DataContext available in XAML.. Look within Pin
grahamoneale16-May-12 14:57
grahamoneale16-May-12 14:57 
Omg! I just discovered the IDataErrorInfo interface *RAGE*
http://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/[^]

This is what I wanted all along to support my dynamic validation!! lol, oh well. So Josh why would we do it this way when we can simply test dynamic properties of our model using an IDataErrorInfo implementation?

Is it just to support generic style validation xaml attributes that can be put anywhere?
QuestionValidation with binding to parent DataContext [solved] Pin
m.gnu.20-Mar-12 22:25
m.gnu.20-Mar-12 22:25 
QuestionFrameworkContentElement? Pin
frank3326-Jan-12 11:39
frank3326-Jan-12 11:39 
QuestionThis is the future Pin
Naixandladders19-Jan-12 20:29
Naixandladders19-Jan-12 20:29 
AnswerRe: This is the future Pin
Josh Smith20-Jan-12 5:56
Josh Smith20-Jan-12 5:56 
QuestionHow to use in Item within item control? Pin
xr280xr11-Jan-12 10:53
xr280xr11-Jan-12 10:53 
QuestionValidationRule Failures vs Warnings Pin
Rasko12310-Mar-11 4:52
Rasko12310-Mar-11 4:52 
GeneralPerformance Implications of Virtual Branching Pin
Member 420288512-Aug-10 8:40
Member 420288512-Aug-10 8:40 
GeneralRe: Performance Implications of Virtual Branching Pin
Josh Smith12-Aug-10 8:43
Josh Smith12-Aug-10 8:43 
GeneralRe: Performance Implications of Virtual Branching Pin
Member 420288513-Aug-10 8:18
Member 420288513-Aug-10 8:18 
QuestionCan you help me please with my sample? Pin
Dudi Avramov1-Jun-10 1:07
Dudi Avramov1-Jun-10 1:07 
GeneralUsing virtual branch in a listview Pin
joostvd18-Mar-10 5:10
joostvd18-Mar-10 5:10 
QuestionMaster slave with the DataContext bridge Pin
Jony12312-Mar-10 1:35
Jony12312-Mar-10 1:35 
QuestionHow to use this in a DataTemplate? Pin
piotri8512-Jan-10 7:48
piotri8512-Jan-10 7:48 
AnswerRe: How to use this in a DataTemplate? Pin
piotri8520-Jan-10 11:56
piotri8520-Jan-10 11:56 
GeneralRe: How to use this in a DataTemplate? Pin
Josh Smith20-Jan-10 12:00
Josh Smith20-Jan-10 12:00 
QuestionBrief explanation, please Pin
david freer16-Nov-09 10:29
david freer16-Nov-09 10:29 
AnswerRe: Brief explanation, please Pin
Josh Smith16-Nov-09 10:50
Josh Smith16-Nov-09 10:50 
GeneralRe: Brief explanation, please Pin
DavidF16-Nov-09 18:21
DavidF16-Nov-09 18:21 
GeneralRe: Brief explanation, please Pin
Josh Smith19-Nov-09 5:06
Josh Smith19-Nov-09 5:06 
GeneralRe: Brief explanation, please Pin
Josh Smith19-Nov-09 5:08
Josh Smith19-Nov-09 5:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.