|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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 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 problemSuppose that we create a simple application which allows us to select a number on a
The two numbers entered by the user are stored in an instance of a simple class named interface IDivisionNumbers : INotifyPropertyChanged
{
int Dividend { get; set; }
int Divisor { get; set; }
}
In the 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 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 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 We cannot add an integer property called Even if we did somehow add an integer dependency property named Similarly, How can we elegantly overcome this seemingly impossible technical barrier to achieve our modest goal? The solutionAttaching 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 Simply put, elements in a virtual branch can bind against the 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 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 Implementing a virtual branchIt 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 bridgeAdd a <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 bridgeAt this point we have an element in place ready to expose the logical tree's <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 It is important to note that the Step 3 – Pull the DataContext off the bridgeThe last step is to make use of the Step 3a – Create a data container classAll nodes in a virtual branch should derive from Below is the class which will hold the divisor to be used by our validation rule: /// <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 containerNow it is time to put 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 bridgeThe final task is to bind the <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 wordsI 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
| ||||||||||||||||||||