In my previous post I demonstrated how an the WPF ElementName style binding can be emulated with Silverlight via an attached behaviour. As a brief recap, the technique involved creating an attached property, which when bound, adds a handler for the elements Loaded event. When the element is loaded, the even handler locates the named element and constructs a binding via a relay object. Here is the attached property in use:
<Rectangle Height="20" Fill="Green"> <local:BindingHelper.Binding> <local:BindingProperties ElementName="Slider" SourceProperty="Value" TargetProperty="Width"/> </local:BindingHelper.Binding> </Rectangle> <Slider x:Name="Slider" Value="20" Minimum="0" Maximum="300"/> |
Where the Rectangle’s Width is bound to the Slider’s Value. For details, and sourcecode, visit this blog post. Here I am going to extend this attached behaviour in order to emulate WPF’s RelativeSource binding.
For much of the time you will want to bind your visual elements to your data objects via their DataContext property. However, there are often times when you want too perform a binding for pure presentation purposes, for example, binding the width of two elements together. In this context WPFs RelativeSource and ElementName binding prove to be powerful and useful features of the binding framework.
Here I will extend the attached behaviour I described in my previous post to add RelativeSource binding capabilities. The properties of the attached property type have been extended:
public class BindingProperties { public string SourceProperty { get; set; } public string ElementName { get; set; } public string TargetProperty { get; set; } public IValueConverter Converter { get; set; } public object ConverterParameter { get; set; } public bool RelativeSourceSelf { get; set; } public string RelativeSourceAncestorType { get; set; } public int RelativeSourceAncestorLevel { get; set; } public BindingProperties() { RelativeSourceAncestorLevel = 1; } } |
With the RelativeSourceSelf, RelativeSourceAncestorType and RelativeSourceAncestorLevel properties being used for relative source bindings. Taking RelativeSourceSelf as our first example, within WPF a RelativeSource.Self property indicates that the source of a binding should be the element which the binding is associated with. (I know – it sounds a bit crazy, but search google, it is surprisingly useful!).
private static void OnBinding( DependencyObject depObj, DependencyPropertyChangedEventArgs e) { FrameworkElement targetElement = depObj as FrameworkElement; targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded); } private static void TargetElement_Loaded(object sender, RoutedEventArgs e) { FrameworkElement targetElement = sender as FrameworkElement; // get the value of our attached property BindingProperties bindingProperties = GetBinding(targetElement); if (bindingProperties.ElementName != null) { // perform our 'ElementName' lookup ... } else if (bindingProperties.RelativeSourceSelf) { // bind an element to itself. CreateRelayBinding(targetElement, targetElement, bindingProperties); } } |
When the attached property becomes attached to our target element it adds a handler for the elements Loaded event (this is the attached behaviour). Within the event handler, we determine whether this is a relative source binding. If this is the case, the CreateRelayBinding method is invoked where the source and target element parameters are the same element. For details of how the CreateRelayBinding method works, see the previous blog post. An example of a relative-source self binding is show below, where a TextBox’s Width property is bound to its Text, if you trype in a new text value, the TextBox Width adjusts accordingly.
<TextBox Text="200" Margin="0,5,0,0"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Width" SourceProperty="Text" RelativeSourceSelf="True"/> </local:BindingHelper.Binding> </TextBox> |
The next type of RelativeSource binding I am going to tackle is the FindAncestor mode. You use this type of binding when you want to bind to an element of a specific type that is located further up the visual tree that the target element. The following code snippet shows how the attached behaviour achieves this type of binding:
private static void TargetElement_Loaded(object sender, RoutedEventArgs e) { FrameworkElement targetElement = sender as FrameworkElement; // get the value of our attached property BindingProperties bindingProperties = GetBinding(targetElement); if (bindingProperties.ElementName != null) { // perform our 'ElementName' lookup ... } else if (bindingProperties.RelativeSourceSelf) { // bind an element to itself. ... } else if (!string.IsNullOrEmpty(bindingProperties.RelativeSourceAncestorType)) { // navigate up the tree to find the type DependencyObject currentObject = targetElement; int currentLevel = 0; while (currentLevel < bindingProperties.RelativeSourceAncestorLevel) { do { currentObject = VisualTreeHelper.GetParent(currentObject); } while (currentObject.GetType().Name != bindingProperties.RelativeSourceAncestorType); currentLevel++; } FrameworkElement sourceElement = currentObject as FrameworkElement; // bind them CreateRelayBinding(targetElement, sourceElement, bindingProperties); } } |
The code above simply navigates up the visual tree to find the n’th element of a given type. When the given type has been located, the relay binding between the two is constructed.
This type of binding is incredibly flexible, the following shows a number of examples:
<UserControl x:Class="SilverlightBinding.PageTwo"> <UserControl.Resources> <local:VisibilityConverter x:Key="VisibilityConverter"/> </UserControl.Resources> <StackPanel x:Name="TheOuterStackPanel" Background="White"> <StackPanel Name="theInnerStackPanel" Width="400"> <!-- bind the textbox width to its text property --> <TextBox Text="200" Margin="0,5,0,0"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Width" SourceProperty="Text" RelativeSourceSelf="True"/> </local:BindingHelper.Binding> </TextBox> <!-- bind the textbox text to this UserControls width property --> <TextBox Margin="0,5,0,0"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Text" SourceProperty="Width" RelativeSourceAncestorType="PageTwo"/> </local:BindingHelper.Binding> </TextBox> <!-- bind the textbox text to the inner stack panels name property --> <TextBox Margin="0,5,0,0"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Text" SourceProperty="Name" RelativeSourceAncestorType="StackPanel"/> </local:BindingHelper.Binding> </TextBox> <!-- bind the textbox text to the outer stack panels name property --> <TextBox Margin="0,5,0,0"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Text" SourceProperty="Name" RelativeSourceAncestorType="StackPanel" RelativeSourceAncestorLevel="2"/> </local:BindingHelper.Binding> </TextBox> <!-- bind a slider's value to its parent grid's height property --> <Grid Height="30" Margin="0,5,0,0" Background="CadetBlue" HorizontalAlignment="Left" > <Slider Width="400" HorizontalAlignment="Left" Minimum="10" Maximum="40"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Value" SourceProperty="Height" RelativeSourceAncestorType="Grid"/> </local:BindingHelper.Binding> </Slider> </Grid> </StackPanel> </StackPanel> </UserControl> |
You can see in the above XAML, examples of relative-source self and find ancestor bindings with a variety of types and ancestor-levels. And here it is in action:
If you type in a new value in the top text box its width will change, or move the slider to see the height of its parent grid changing. And all without any code-behind of course!
Many of these effects shown above could be performed via element-name bindings. However, you are not always the creator, or template provider for all the visual elements rendered to screen. One common example is that of the ItemsControl, the ListBox being an example of this type of control. Here you supply a DataTemplate, with your visual elements being rendered inside a ListBoxItem container. Therefore, there is no way to bind to the ListBoxItem via an element name or otherwise. However, you can reach the ListBoxItem by navigating up the tree using a relative-source binding as show below:
<ListBox Name="grid" Width="200" HorizontalAlignment="Left"> <ListBox.ItemTemplate> <DataTemplate> <Grid Width="180"> <TextBlock Text="{Binding Forename}"/> <Ellipse Width="10" Height="10" Fill="Red" HorizontalAlignment="Right"> <local:BindingHelper.Binding> <local:BindingProperties TargetProperty="Visibility" SourceProperty="IsSelected" Converter="{StaticResource VisibilityConverter}" RelativeSourceAncestorType="ListBoxItem"/> </local:BindingHelper.Binding> </Ellipse> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> |
In the above example we have a DataTemplate which contains an ellipse. As the ListBox generates each ‘row’, it creates a ListBoxItem and populates it with the visual elements from our data template. When our ellipse is created, and loaded, the attached behaviour fires, navigating up the visual tree to find the first ListBoxItem it encounters. When it finds it, it creates a single instance of our relay object, binding both the ListBoxItem.IsSelected and Ellipse.Visibilty (via a suitable value converter) together via the relay object.
And here it is in action (click an item to see the ellipse) …
You can download a visual studio project with all the sourcecode: silverlightrelativesourcebinding.zip.
Enjoy, Colin E.
Tags: attached behaviour, binding, silverlight


email: ceberhardt@scottlogic.co.uk
on Google+



I found a memory leak in your class. You don’t remove items out of the dictionary when they are unloaded. A big PIA when switching pages.
Here is the fix:
Add a property to RelayBindingKey:
private struct RelayBindingKey
{
public DependencyProperty dependencyObject;
public FrameworkElement frameworkElement;
public FrameworkElement targetFrameworkElement;
}
Then set the targetFrameworkElement in CreateRelayBinding:
private static void CreateRelayBinding(FrameworkElement targetElement, FrameworkElement sourceElement,
BindingProperties bindingProperties)
{
…
// create a key that identifies this source binding
RelayBindingKey key = new RelayBindingKey()
{
dependencyObject = sourceDependencyProperty,
frameworkElement = sourceElement,
targetFrameworkElement = targetElement
};
…
}
Then set that on unload of the element you remove the eventhandler and the dictionary entry:
private static void OnBinding(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement targetElement = depObj as FrameworkElement;
targetElement.Loaded += TargetElement_Loaded;
targetElement.Unloaded += TargetElement_Unloaded;
}
private static void TargetElement_Unloaded(object sender, RoutedEventArgs e)
{
FrameworkElement targetElement = sender as FrameworkElement;
if (targetElement != null)
{
targetElement.Loaded -= TargetElement_Loaded;
targetElement.Unloaded -= TargetElement_Unloaded;
}
var elementToDelete = relayBindings.Keys.FirstOrDefault(x => x.targetFrameworkElement.Equals(targetElement));
if (elementToDelete.dependencyObject != null)
{
relayBindings.Remove(elementToDelete);
Debug.WriteLine(“Element deleted”);
}
}
[...] way from the data context of the item back to the context of the items control, so people like Colin Eberhardt and Kiener have come up with clever ways to work around this [...]
Thank you very much!
It solves the problem for binding within a DataTemplate!
Not even ElementName could do that! Great!
Cheers!
In my blog I described the BoundToSelfControl, which binds itself to itself and in the same time it saves what was passed through DataContext in special field. Take a look at http://dotnetfollower.com/wordpress/2011/06/silverlight-for-windows-phone-7-how-to-bound-usercontrol-to-itself/
Thanks!
.NET Follower (http://dotnetfollower.com)
Thanks a lot! This solved my frustrating problem!
I’m a Silverlight newbie and I found your owesome tools googling for some solutions to my problems. At first sight your tools solve them, but then I’ve got some new ones.
I’m trying your code for relative binding and for multibinding, and I have the following questions:
1. How can I define relative binding for more than one property in a control ? ( for example, inside a listboxitem, I need to bind an Ischecked property of a control to the IsSelected property of the listboxitem, and at the same time, I need to bind the Width property of the same control to the Width of the listbox
2.- Why didn’t you implemented relative binding in multibinding – or did I miss it -?
thank uou
Hi,
To have multiple relative source bindings you could update the code as per my multibinding solution:
http://www.scottlogic.co.uk/blog/colin/2010/08/silverlight-multibinding-updated-adding-support-for-elementname-and-twoway-binding/
To answer your second question, I like to provide working solutions to interesting problems, however I am not trying to create some all-encompassing framework of Silverlight extensions. Therefore my solutions often will not integrate together fully.
Regards, Colin E.
Hi,
your blog is helpful but i want opposite like if ellipse is visible then listitem sholud be selected i.e. ListBoxItem’s IsSelected property depends on Ellipse Visibility.
Please tell me the solution.
Thanks in advance
How can I bind to Command in ViewModel? I want delete Item it self from Listbox. MainPage.xaml DataContext is binded to MainPageViewModel. DeleteItemCommand is in MainPageViewModel and how to bind a button command in ItemTemplate to DeleteItemCommand?
P.S.: sorry, my english is poor.
[...] 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 [...]
Thanks Colin, great tutorial!
Hi Drew,
Glad you like it – you should check out the multi-binding code I wrote (http://www.scottlogic.co.uk/blog/colin/2010/05/silverlight-multibinding-solution-for-silverlight-4/), it is much more fun
Colin E.
This code leaks memory very badly. Just wanted to give a heads up to anyone looking to put this into production.
@Jason,
Thanks for the feedback – can you please indicate how you detected this memory leak and its source?
Regards, Colin E.
i found it too. the relay binding won’t let go. don’t know how to fix it. i was using the RedGate memory profiler tool.
Has this been addressed at all? It looked like a great solution but unusable if its not production ready.
i’m struggling… what am i missing?
i paste this:
But…..
“Namespace prefix local is not defined”
swapping ‘local’ for my namespace gives me the same thing, ‘not defined’.
i change local: to x: and i get “binding hellper is not support in a silverlight project”
i’ve seen the “local” notation used elsewhere. What is that a stand in for? Is there an assembly ref to make ‘local’ work?
thanks for any hints.
@aleksandr,
This is explained in the MSDN docs:
http://msdn.microsoft.com/en-us/library/ms747086.aspx#Mapping_To_Custom_Classes_and_Assemblies
Does this work for button commands inside a Silverlight DataGrid? If so, could you give me a xaml example?
The following doesn’t work for me:
@datagrid (is that really your name?)
It should work in the DataGrid, but is not something i have tried. You could debug your application, watching teh loop where it navigates from Parent to Parent to see if you can spot teh type which you are looking for.
Thanks Colin, Can you please tell me if this can be done on Silverlight 3.. also whats the “local:” namespace being used everywhere. Sorry for my ignorance .. I am still a beginner on silverlight.
Hi Sanchit,
I think this solution should work in SL3. The ‘local:’ namespace is a mapping from a CLR namespace to a XAML (XML) namespace. See the MSDN documentation here:
http://msdn.microsoft.com/en-us/library/ms747086.aspx#Mapping_To_Custom_Classes_and_Assemblies
Colin E.
Thanks for the fantastic post; this will save me hours of time!
How would you modify the code to bind the relative object itself rather than a property on said object? I have a need to bind the parent listbox to a property on a custom ListBoxItem template contentcontrol.
Hi Todd, I have not tried this myself, however to bind to an object rather than a property of an object you need a binding without a path. Look at the CreateRelayBinding method where the bindings themselves are create – you can probably just make your change here.
Colin E.
THIS IS SOOOOO AWESOME
u da man!!!!!
Thanks Marlon
I know its a little late response from my side but still.
Thanks @Colin
Great little helper! Unfortunately, it doesn’t work when your source property is read-only.
For instance, I am trying to bind the Width of a TextBlock to the ActualWidth of its parent StackPanel. This relay mechanism throws when attaching the binding to the sourceElement. Setting the binding to OneWay doesn’t do it either, because by setting a binding on the source element, you are essentially trying to SET ActualWidth when the initial binding occurs.
I couldn’t think of any way around it, though I haven’t thought too hard about it yet.
Brian
Thanks for this article. How can I bind two or more properties of the same element using this?
@Udit,
Hi, I think it should be a straightforward task to change the
BindingHelper.Bindingproperty so that it contains a collection ofBindingPropertiesinstances. You then simply change the attached property to iterate over this collection creating the bindings.Regards, Colin E.
[...] Silverlight 2 doesn’t support WPF’s ElementName or RelativeSource binding. The simplest solution is to use a “relay” class and have multiple UI elements binding to it. There are some great posts about it here, here and here. [...]
Yeah, we’re all waiting with baited breath for more info on the features in SL 3.
It will help us define which workarounds to keep, and which to dump(lol).
Thanks for this article, and the companion article on ElementName binding.
I wonder if you know whether either of these items will be provided in Silverlight 3?
Hi Fallon,
Good question … I am guessing that ElementName binding will make it into Silverlight 3, it has been hinted at a few times on the silverlight forums. Personally I doubt RelativeSource binding (other than perhaps RelativeSource-Self) will make it into Silverlight 3, whilst very powerful, it is not used terribly often in WPF.
Although we might find the answer to your question very shortly:
http://visitmix.com/News/Silverlight-3-Sessions-at-MIX09
Regards, Colin E.
RelativeSource binding in Silverlight…
Thank you for submitting this cool story – Trackback from DotNetShoutout…