1. Problem
You would like to data bind
some property of a XAML element to a property on another element on the
page or to a different property on the source element itself.
2. Solution
To bind to a property on another element, name the source element, and then use the ElementName property on the binding. To bind to a property on the same element, use the RelativeSource property on the binding.
3. How It Works
You have walked through binding a property (target) of a XAML
element to a property (source) on a CLR object. This is one possible
scenario. You may encounter situations where the binding scenarios are
slightly different in terms of the binding source.
3.1. Binding to Another Element
In this scenario, a property on
an element on a page is data bound to a property on another element on
the same page. You can achieve this by setting the Binding.ElementName property on the binding declaration to the name of the source element and the Binding.Path property to the name of the property on the element. Note that this feature was introduced in Silverlight 3.
For an example, consider a Slider control on a page, with a TextBox on the same page displaying the current value of the Slider. This snippet illustrates such a binding arrangement:
<TextBox Text="{Binding Path=Value,ElementName=sliderSource, Mode=OneWay}" />
<Slider x:Name="sliderSource"
Minimum="0"
Maximum="100" />
Note that the Binding.ElementName on the TextBox.Text property points to the Slider on the page. The rest of the binding declaration follows the usual binding rules; for instance, if you were to set the Binding.Mode value to TwoWay, editing the TextBox.Text to a permissible value within the Slider's range would actually reset the Slider thumb to that value.
3.2. Binding to Self
In this scenario, a property
on an element is data bound to another property on the same element.
This is made possible by using the Binding.RelativeSource property. The Binding.RelativeSource property can be set to one of the two values specified in the System.Windows.Data.RelativeSourceMode enumeration: Self and TemplatedParent. Using the RelativeSourceMode.Self value allows the binding to use the element itself as a source for the binding.
In the following code snippet, you bind the ForeGround property of a TextBox to the Text property of the same TextBox. The intent is that if the user types in a valid color name in the TextBox,
the edited text is displayed in that color. Since there is no
conversion from string to a brush, you rely on a value converter to do
the conversion for you:
<TextBox
Foreground="{Binding Path=Text,RelativeSource={RelativeSource Self},
Converter={StaticResource REF_ColorStringToBrushConverter}" />
Note the syntax of the RelativeSource attribute setting in the preceding binding expression. The format RelativeSource={RelativeSource <RelativeSourceMode>} is the required syntax.
3.3. Binding to the TemplatedParent
An instance of a control to which a control template is applied is the TemplatedParent to any element within the control template definition. The following snippet shows a possible binding where a TextBox within a control template is binding its Foreground property to the TemplatedParent's Foreground property:
<ControlTemplate TargetType="MyControl">
...
<TextBox
Foreground=
"{Binding Path=Foreground,RelativeSource={RelativeSource TemplatedParent}}" />
...
</ControlTemplate>
This will cause the TextBox to inherit the same Foreground brush that the developer decides to set on any particular instance of the control named MyControl.
4. The Code
The code sample for this
recipe illustrates the element binding and self binding techniques
discussed above. The sample uses the new 3-D capabilities in Silverlight
to rotate a simple visual along the X, Y, and Z axes of a 3-D plane.
The visual contains a Grid with the current angle values for each rotation axis displayed within a Border, and three separate Sliders are used to control the rotation angles.
Figure 1 shows the sample in action.
The majority of the code is encapsulated in a user control named RotatorDemoControl. Listing 1 shows the complete XAML for RotatorDemoControl.
Listing 1. XAML for RotatorDemoControl
<UserControl x:Class="Recipe4_5.RotatorDemoControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:Recipe4_5"
>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="0.75*" />
<RowDefinition Height="0.25*" />
</Grid.RowDefinitions>
<Grid x:Name="target"
Width="275"
Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border BorderThickness="2"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
BorderBrush="Red"
Background="AliceBlue"></Border>
<TextBlock Text="Rotation X"
Margin="3,0,0,0" />
<TextBlock Text=":"
Grid.Column="1" />
<TextBlock Text="{Binding Xangle}"
Grid.Column="2"
Margin="0,0,0,3" />
<TextBlock Text="Rotation Y"
Grid.Row="1"
Margin="3,0,0,0" />
<TextBlock Text=":"
Grid.Column="1"
Grid.Row="1" />
<TextBlock Text="{Binding Yangle}"
Grid.Column="2"
Grid.Row="1"
Margin="0,0,0,3" />
<TextBlock Text="Rotation Z"
Grid.Row="2"
Margin="3,0,0,0" />
<TextBlock Text=":"
Grid.Column="1"
Grid.Row="2" />
<TextBlock Text="{Binding Zangle}"
Grid.Column="2"
Grid.Row="2"
Margin="0,0,0,3" />
<Grid.Projection>
<PlaneProjection x:Name="gridProjection" />
</Grid.Projection>
</Grid>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center"
Grid.Row="1">
<StackPanel Orientation="Horizontal"
Margin="0,10,0,10">
<TextBlock Text="Rotate on X Axis: " />
<Slider Minimum="0"
Maximum="360"
x:Name="sliderX"
Value=
"{Binding ElementName=gridProjection, Mode=TwoWay, Path=RotationX}"
Width="125" />
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,10,0,10">
<TextBlock Text="Rotate on Y Axis: " />
<Slider Minimum="0"
Maximum="360"
x:Name="sliderY"
Value=
"{Binding ElementName=gridProjection, Mode=TwoWay, Path=RotationY}"
Width="125" />
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,10,0,10">
<TextBlock Text="Rotate on Z Axis: " />
<Slider Minimum="0"
Maximum="360"
x:Name="sliderZ"
Value=
"{Binding ElementName=gridProjection, Mode=TwoWay, Path=RotationZ}"
Width="125" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
|
As shown in Listing 1, the PlaneProjection named gridProjection projects the Grid to a 3-D plane. The PlaneProjection type exposes three properties, namely RotationX, RotationY and RotationZ, each of which can be independently set to an angle value between 0 and 360 degrees to rotate the Grid along that axis.
If you note the binding expression for the Value property of the Slider named sliderX, you will see that it is bound directly in a TwoWay mode to the RotationX property of gridProjection, utilizing the ElementName binding attribute. The range for sliderX is set to vary between 0 and 360, and changing this value will cause gridProjection to rotate along the X axis by that amount. The other two Sliders, sliderY and sliderZ, follow a similar arrangement to affect the RotationY and RotationZ properties of the gridProjection element.
Listing 2 shows the codebehind for the RotatorDemoControl.
Listing 2. Codebehind for RotatorDemoControl
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace Recipe4_5
{
public partial class RotatorDemoControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public RotatorDemoControl()
{
InitializeComponent();
sliderX.ValueChanged +=
new RoutedPropertyChangedEventHandler<double>((s, e) =>
{
Xangle = sliderX.Value;
});
sliderY.ValueChanged +=
new RoutedPropertyChangedEventHandler<double>((s, e) =>
{
Yangle = sliderY.Value;
});
sliderZ.ValueChanged +=
new RoutedPropertyChangedEventHandler<double>((s, e) =>
{
Zangle = sliderZ.Value;
});
}
private double _Xangle = default(double);
public double Xangle
{
get
{
return _Xangle;
}
set
{
if (value != _Xangle)
{
_Xangle = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Xangle"));
}
}
}
private double _Yangle = default(double);
public double Yangle
{
get
{
return _Yangle;
}
set
{
if (value != _Yangle)
{
_Yangle = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Yangle"));
}
}
}
private double _Zangle = default(double);
public double Zangle
{
get
{
return _Zangle;
}
set
{
if (value != _Zangle)
{
_Zangle = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Zangle"));
}
}
}
}
}
|
As Listing 2 shows, the RotatorDemoControl control class exposes three properties named Xangle, Yangle, and Zangle
with property change notification enabled. These values are updated
when the corresponding slider values are changed, as shown in the event
handlers of the ValueChanged events of the Sliders, in the constructor of the RotatorDemoControl class.
If you refer to the RotatorDemoControl XAML in Listing 1, you will note that there are three TextBlocks inside the rotated Grid that are respectively bound to these properties. The intention is to display the angle values as the Grid is rotated. Looking at the binding statements for these TextBlocks, you will note that they simply provide the Binding.Path values pointing to the properties on RotatorDemoControl. But how do the bindings know to use the control class as its data source? Take a look at Listing 3 that shows the XAML for the MainPage, which actually declares the RotatorDemoControl user control.
Listing 3. XAML for MainPage
<UserControl x:Class="Recipe4_5.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Recipe4_5"
Width="640"
Height="480">
<Grid x:Name="LayoutRoot"
Background="White">
<local:RotatorDemoControl
DataContext="{Binding RelativeSource={RelativeSource Self}}" />
</Grid>
</UserControl>
|
Listing 3 shows that the DataContext for the RotatorDemoControl is bound to itself because the RelativeSource attribute is set to Self. This then sets the RotatorDemoControl instance as the data source for the TextBlock bindings referred to earlier and helps display the angle values as notified through the corresponding properties on the RotatorDemoControl class.