Silverlight 3 introduced the RelativeSource Binding that is well known from WPF. Unfortunately Silverlight only supports the two modes Self and TemplatedParent.
I don’t know why the FindAncestor mode is missing in Silverlight’s RelativeSource binding, but there is a real need for that in Silverlight too. I’m finding me quite often in a dead-end ifI want to set a binding to a command or value in the view model within a list box item template. The problem in the example below is that the list box item has its own data context set to an item in the items-collection.
In the case above I really need a relative binding to set a binding to the RemoveCommand and to the global Picklist collection as comboxbox source. On solution is to create a bidirectional relationship between the view models behind the scene in order to get a reference to the parent view model within the child view model. But this not the solution I was looking for.
After some googling I found a binding helper written by Colin Eberhardt which enables it to use a relative binding with help of an attached dependency property.
Explained in brief: Colin uses an attached property to set his own relative source binding configuration for an specific element. When the attached property becomes attached to the target element it adds a handler for the elements loaded event. Within the event handler, he walks up the visual tree to find the specified ancestor and constructs a binding expression between the source and target properties.
Based on Colin’s idea I’ve created a similar implementation which provides some different behaviors:
- Support for a list of relative binding. This allows binding more than one property of an element.
- It does not use a relay object in the middle of the binding. It is a pure binding without any custom code in between. This gain in a better performance, especially when using a storyboard to animate the data bound value.
- I have added some special type of relative binding mode which is called as ParentDataContext mode. In that mode it walks up the visual tree until it finds a new data context. This is very helpful when having templates in an ItemsControl, ListBox or DataGrid.
- Support for non-dependency property as source, e.g. direct usage of view model properties.
- Support for property path syntax for the source property, e.g. DataContext.Person.Name
- The AncestorType allows to set a base class as type criteria instead of the concrete type, e.g. if the concrete element is of type Grid, then the AncestorType can be set to Panel.
- Allows to set the relative binding for attached properties too, e.g. to bind TooltipService.Tooltip property.
- Allows to use OneWay or OneTime binding mode.
- Allows to set any other binding parameter such as ValidatesOnNotifyDataErrors, ValidatesOnExceptions, ValidatesOnDataErrors, NotifyOnValidationError and ConverterCulture.
- Enables to set the XAML namespace for controls which are not in the core-control assembly.
How to use
Let’s have a look at some examples.
1. Simply relative binding with ancestor type
<ComboBox>
<local:BindingHelper.Binding>
<local:RelativeSourceBinding Path="DataContext.Picklist"
TargetProperty="ItemsSource" RelativeMode="FindAncestor"
AncestorType="UserControl" />
</local:BindingHelper.Binding>
</ComboBox>
2. Bind two or more properties by adding a list of binding definitions
<ComboBox>
<local:BindingHelper.Binding>
<local:BindingList>
<local:RelativeSourceBinding Path="DataContext.Picklist"
TargetProperty="ItemsSource" RelativeMode="FindAncestor"
AncestorType="UserControl" />
<local:RelativeSourceBinding Path="DataContext.Tooltip"
TargetProperty="(ToolTipService.ToolTip)" RelativeMode="FindAncestor"
AncestorType="UserControl" />
</local:BindingList>
</local:BindingHelper.Binding>
</ComboBox>
3. Bind any attached dependency property
<local:RelativeSourceBinding Path="DataContext.Tooltip"
TargetProperty="(ToolTipService.ToolTip)" RelativeMode="FindAncestor"
AncestorType="UserControl" />
4. Bind an attached dependency property from any assembly. Just specify the XAML namespace as in the example below
<local:RelativeSourceBinding Path="DataContext.Tooltip"
TargetProperty="(DemoAttachedElement.Value)"
TargetNamespace="clr-namespace:RelativeSourceBindingDemo;assembly=RelativeSourceBindingDemo"
RelativeMode="FindAncestor" AncestorType="UserControl" />
5. Use the parent data context instead an ancestor type
<local:RelativeSourceBinding Path="RemoveCommand" TargetProperty="Command"
RelativeMode="ParentDataContext" />
6. The mode ParentDataContext is set as default behavior. So in most cases you can just write xaml as following:
<Button Content="Remove" CommandParameter="{Binding}">
<local:BindingHelper.Binding>
<local:RelativeSourceBinding Path="RemoveCommand" TargetProperty="Command" />
</local:BindingHelper.Binding>
</Button>
<ComboBox Grid.Column="1">
<local:BindingHelper.Binding>
<local:BindingList>
<local:RelativeSourceBinding Path="Picklist" TargetProperty="ItemsSource"/>
<local:RelativeSourceBinding Path="Tooltip" TargetProperty="(ToolTipService.ToolTip)" />
</local:BindingList>
</local:BindingHelper.Binding>
</ComboBox>
7. Use a converter and other binding settings
<local:RelativeSourceBinding Path="Application.IsEnabled"
TargetProperty="Visibility" Converter="{StaticResource VisibilityConverter}"
ValidatesOnNotifyDataErrors="True" ValidatesOnExceptions="True"
ValidatesOnDataErrors="True" NotifyOnValidationError="True" />
Source Code & Demo Project
Here you can find the full source code within a demo project.