A lot of articles those are talking about binding and sources, and how to bind
properties each other using StaticResources, DynamicResources, although you can
find information about the RelativeSource and its use cases but not with more
details even in Microsoft documentations. In this article, I will expose the use
cases of the RelativeSources in WPF.
The RelativeSource is a markup extension that is used in particular binding
cases when we try to bind a property of a given object to another property of
the object itself, when we try to bind a property of a object to another one of
its relative parents, when binding a dependency property value to a piece of
XAML in case of custom control development and finally in case of using a
differential of a series of a bound data. All of those situations are expressed
as relative source modes. I will expose all of those cases one by one.
1. Mode Self:
Imagine this case, a rectangle that we want that its height is always equal to
its width, a square let's say. We can do this using the element name
<Rectangle
Fill="Red"
Name="rectangle"
Height="100"
Stroke="Black"
Canvas.Top="100"
Canvas.Left="100"
Width="{Binding
ElementName=rectangle,
Path=Height}"/>
But in this above case we are obliged to indicate the name of the binding
object, namely the rectangle. We can reach the same purpose differently using
the RelativeSource
<Rectangle
Fill="Red"
Height="100"
Stroke="Black"
Width="{Binding
RelativeSource={RelativeSource
Self},
Path=Height}"/>
For that case we are not obliged to mention the name of the binding object and
the Width will be always equal to the Height whenever the height is changed.
If you want to parameter the Width to be the half of the height then you can do
this by adding a converter to the Binding markup extension.
Let's imagine another case now:
<TextBlock
Width="{Binding
RelativeSource={RelativeSource
Self},
Path=Parent.ActualWidth}"/>
The above case is used to tie a given property of a given element to one of its
direct parent ones as this element holds a property that is called Parent. This
leads us to another relative source mode which is the FindAncestor one.
2. Mode FindAncestor
In this case, a property of a given element will be tied to one of its parents,
Of Corse. The main difference with the above case is the fact that, it's up to
you to determine the ancestor type and the ancestor rank in the hierarchy to tie
the property. By the way try to play with this piece of XAML
<Canvas
Name="Parent0">
<Border
Name="Parent1"
Width="{Binding
RelativeSource={RelativeSource
Self},
Path=Parent.ActualWidth}"
Height="{Binding
RelativeSource={RelativeSource
Self},
Path=Parent.ActualHeight}">
<Canvas
Name="Parent2">
<Border
Name="Parent3"
Width="{Binding
RelativeSource={RelativeSource
Self},
Path=Parent.ActualWidth}"
Height="{Binding
RelativeSource={RelativeSource
Self},
Path=Parent.ActualHeight}">
<Canvas
Name="Parent4">
<TextBlock
FontSize="16"
Margin="5"
Text="Display
the name of the ancestor"/>
<TextBlock
FontSize="16"
Margin="50"
Text="{Binding
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type
Border},
AncestorLevel=2},Path=Name}"
Width="200"/>
</Canvas>
</Border>
</Canvas>
</Border>
</Canvas>
The above situation is of two TextBlock elements those are embedded within a
series of borders and canvas elements those represent their hierarchical
parents. The second TextBlock will display the name of the given parent at the
relative source level.
So try to change AncestorLevel=2 to AncestorLevel=1 and see what happens. Then
try to change the type of the ancestor from AncestorType=Border to AncestorType=Canvas
and see what's happens.
The displayed text will change according to the Ancestor type and level. Then
what's happen if the ancestor level is not suitable to the ancestor type? This
is a good question, I know that you're about to ask it. The response is no
exceptions will be thrown and nothings will be displayed at the TextBlock level.
3. TemplatedParent
This mode enables tie a given ControlTemplate property to a property of the
control that the ControlTemplate is applied to. To well understand the issue
here is an example bellow
<Window.Resources>
<ControlTemplate
x:Key="template">
<Canvas>
<Canvas.RenderTransform>
<RotateTransform
Angle="20"/>
</Canvas.RenderTransform>
<Ellipse
Height="100"
Width="150"
Fill="{Binding
RelativeSource={RelativeSource
TemplatedParent},
Path=Background}">
</Ellipse>
<ContentPresenter
Margin="35"
Content="{Binding
RelativeSource={RelativeSource
TemplatedParent},Path=Content}"/>
</Canvas>
</ControlTemplate>
</Window.Resources>
<Canvas
Name="Parent0">
<Button
Margin="50"
Template="{StaticResource
template}"
Height="0"
Canvas.Left="0"
Canvas.Top="0"
Width="0">
<TextBlock
FontSize="22">Click
me</TextBlock>
</Button>
</Canvas>
If I want to apply the properties of a given control to its control template
then I can use the TemplatedParent mode. There is also a similar one to this
markup extension which is the TemplateBinding which is a kind of short hand of
the first one, but the TemplateBinding is evaluated at compile time at the
contrast of the TemplatedParent which is evaluated just after the first run
time. As you can remark in the bellow figure, the background and the content are
applied from within the button to the control template.
4. PreviousData
This is the most ambiguous and the less used mode of the RelativeSource, I mean
the PreviousData one. The PreviousData is used for particular cases. Its purpose
is to tie the given property to another property with a particular assignment; I
mean it assigns the previous value of the property to the bound one. In other
word, if you have a TextBox that has a text property and another control that
has a value property that holds data. Say that value is actually 5 and it was 3
just before. The 3 is assigned to the text property of the TextBox and not 5.
This leads to the idea that this kind of RelativeSource is frequently used with
the items controls.
To understand the phenomenon of the RelativeSource let's expose this sample. I
will add an ItemsControl into the scene and I will aliment it from a custom
collection
<Grid>
<ItemsControl></ItemsControl>
</Grid>
This ItemsControl is alimented using this collection:
public
class Items :
ObservableCollection<Item>
{
public Items()
{
Add(new
Item { Value = 80.23 });
Add(new
Item { Value = 126.17 });
Add(new
Item { Value = 130.21 });
Add(new
Item { Value = 115.28 });
Add(new
Item { Value = 131.21 });
Add(new
Item { Value = 135.22 });
Add(new
Item { Value = 120.27 });
Add(new
Item { Value = 110.25 });
Add(new
Item { Value = 90.20 });
}
}
It's an ObservableCollection of type
Item that I had developed and that holds a simple property which is called
Value, it is of type double.
public
class Item :INotifyPropertyChanged
{
private double
_value;
public double
Value
{
get {
return _value; }
set { _value =
value; OnPropertyChanged("Value");
}
}
#region
INotifyPropertyChanged Members
public event
PropertyChangedEventHandler PropertyChanged;
#endregion
protected void
OnPropertyChanged(string PropertyName)
{
if (null
!= PropertyChanged)
{
PropertyChanged(this,
new
PropertyChangedEventArgs(PropertyName));
}
}
}
Now, to bind the
ItemsControl to the collection data, I will set the DataContext
property of the whole window to the collection at the Window constructor level.
public
Window1()
{
InitializeComponent();
this.DataContext = new
Items();
}
And then I'll specify the binding of the
ItemsControl
ItemsControl
ItemsSource="{Binding}"
Margin="10"
Then the result will be like this. I mean not presentable.
Therefore, we have to apply some features to enhance the visual of that
representation.
<ItemsControl
ItemsSource="{Binding}"
Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border
CornerRadius="3"
BorderThickness="3"
Width="80"
Height="{Binding
Value}"
Margin="0,0,35,0"
BorderBrush="Violet"
Background="BlueViolet">
<TextBlock
Text="{Binding
Value}"
FontWeight="bold"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="Wheat">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleY="-1"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleY="-1"/>
<TranslateTransform
Y="250"/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
Shortly, I will describe the above XAML. First, the ItemsPanel will arrange the
items within an horizontal StackPanel for more information about the ItemsPanel
please refer to this link
http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemspanel.aspx
Second, the DataTemplate is used to present the data as a border; the border
height is bound to the Value of the item class to reflect the Values that the
collection holds. The same border includes a TextBlock that displays the Value
of the Item object.
For more information about te DateTemplate please refer to this MSDN link
http://msdn.microsoft.com/en-us/library/system.windows.datatemplate.aspx
The RenderTransform is used to emphasize the position of the items in the scene.
For more information about transformation please refer to this MSDN link
http://msdn.microsoft.com/en-us/library/system.windows.media.transform.aspx
The result of the presentation will be as follow
Now, the main purpose of this demo is to show the characteristic of the
RelativeSource.PreviousData mode.
The idea consists of adding a TextBox and tie the Text property to the Value of
the previous border in the items' list. Something that seems to be as the bellow
representation
As you can note, each TextBlock represents the previous value that the previous
item holds. This is in fact the magic of the PreviousData of the RelativeSource
mode.
The idea is to add the TextBlock to the DataTemplate as Follow
<TextBlock
FontSize="14"
FontWeight="bold"
Margin="20"
Text="{Binding
RelativeSource={RelativeSource
PreviousData},
Path=Value}">
<TextBlock.RenderTransform>
<ScaleTransform
ScaleY="-1"/>
</TextBlock.RenderTransform>
</TextBlock>
Then the whole picture will be
<Grid>
<ItemsControl
ItemsSource="{Binding}"
Margin="10">
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleY="-1"/>
<TranslateTransform
Y="250"/>
</TransformGroup>
</ItemsControl.RenderTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock
FontSize="14"
FontWeight="bold"
Margin="20"
Text="{Binding
RelativeSource={RelativeSource
PreviousData},
Path=Value}">
<TextBlock.RenderTransform>
<ScaleTransform
ScaleY="-1"/>
</TextBlock.RenderTransform>
</TextBlock>
<Border
CornerRadius="3"
BorderThickness="3"
Width="80"
Height="{Binding
Value}"
Margin="0,0,35,0"
BorderBrush="Violet"
Background="BlueViolet">
<TextBlock
Text="{Binding
Value}"
FontWeight="bold"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="Wheat">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleY="-1"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Of Course, we could do more that this using RelativeSource.PreviousData but this
will be enough to have a general consistent idea about that mode. That's all
Good Dotneting!!!