RelativeSource Binding with FindAncestor mode in Silverlight

Silverlight 3 introduced the RelativeSource Binding that is well known from WPF. Unfortunately Silverlight only supports the two modes Self and TemplatedParent.

image 

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.

image

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.

clip_image001

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.

About these ads
Tagged ,

39 thoughts on “RelativeSource Binding with FindAncestor mode in Silverlight

  1. Colin E. says:

    A very nice extension to my original implementation. Great work!

    Colin E.

  2. Joe Gershgorin says:

    Good stuff, thanks!

    It didn’t quite meet my needs since I need to set a property on an object that derives from DependencyObject and not FrameworkElement (Blend Behavior property). But this gives me the basis to figure out a solution once I dedicate some time to it.

    In the CreateBinding method I took out the requirement for the property path to be set, sometimes you want the source to be the datacontext of the relative source itself and not a property off of it.

  3. silverlame says:

    Does your implementation leak memory like Colin’s?

  4. beatkiener says:

    No, because the binding helper sets up a standard binding between the source property and the target property. And the binding helper itself is simply attached to an UIElement via an attached dependency property and gets released until the UIElement is released.

    I was not aware about a possible memory leak in Colin’s code. But after a check in his code I think it is possible that his code leaks, because he uses a relay object (class ValueObject) in the middle of the source property and the target property and this relay object is stored in a static dictionary. Unfortunately the relay object gets never removed from the dictionary this causes the mentioned memory leak I think. In my opinion this could be solved with a few little changes in his code.

  5. Kevin says:

    Would it be possible to use this in conjunction with the EventToCommand behavior of MVVMLight? I’m building a SL3 app (specifically a WP7 app) and I’m using MVVMLight in part due to the lack of Command binding in SL3.

    I have the same problem of trying to bind to a RelayCommand on my view model from within an ItemTemplate of a Listbox. After seeing your post I thought it might be possible. I copied your RelativeBinding code into my project, fixed a couple of compile errors for SL3/4 differences (like String.IsNullOrWhitespace) and added this XAML to a button within my ItemTemplate.

    I’m not sure how/if I can bind the CommandParameter to the current item being bound in the listbox. But regardless of that, it doesn’t seem to be firing the command at all. Do you have any insight as to if this is even possible, and if so what I might be doing wrong? Thanks!

  6. Great stuff. I just implemented into a test app in about 5 minutes and it grabs my parent view models datacontext perfectly. I love the built in data context search, less coding.

  7. Kevin says:

    No, I never did get it working, although I didn’t work on it very long. We ended up abandoning most of our command bindings and using simple Click event handlers in an effort to improve our listbox performance. Unfortunately the performance of lists in WP7 isn’t very good and a lot of the standard techniques you would use in desktop SL/WPF (like command bindings, value converters, etc.) contribute to the performance problems.

  8. Will says:

    It works with WP7 once you get past the compilations errors, most of which are reference issues and the already noted IsNullOrWhitespace(). There are two Error bindings you need to comment out because they are not supported towards the bottom of BindingHelper. Once it compiled I used the following to get a listbox within a listbox to see the parent data context.

    
                                                
                                                    
                                                
                                            
    
    
  9. Will says:

    The site won’t accept any tags… here is the breakdown of the tags without brackets:

    BindingHelper.Binding
    BindingList
    RelativeSourceBinding Path=VMPropertyYouWant TargetProperty=YourControlsProperty RelativeMode=ParentDataContext

  10. Nona Mills says:

    It works with WP7 once you get past the compilations errors, most of which are reference issues and the already noted IsNullOrWhitespace(). There are two Error bindings you need to comment out because they are not supported towards the bottom of BindingHelper. Once it compiled I used the following to get a listbox within a listbox to see the parent data context.

  11. Miroslav says:

    First, I want to thank you as in WP7 this is really a great problem or at least for the MVVM fans. However I have a problem beceause of the following: ConverterParameter=”{Binding KeyName}”. KeyName is a property of the item (btw below is the datatemplate for my itemscontrol). Any advice on this issue ?

  12. Magiel says:

    Nice work, But would appreciate it greatly to be notified when a solution is found for issue Miroslav has posted.

  13. Beat,

    If you wrap the DataGrid in another control, like a listbox or accordion which is bound, the parent resolves to the ListBox not the UserControl, which for me gets the same DataContext. Is there a way to go higher using the ParentDataContext (TargetNamespace)?

    I have also tried using RelativeMode=”FindAncestor” AncestorType=”UserControl” but I can’t find a DataContext on ancestor when this runs in BindingHelper

    CreateBinding(targetElement, ancestor, bindingConfiguration);

    Any thoughts?

  14. Derek says:

    Very useful, nice work!

  15. harry says:

    I, also, would like to know a fix for the ConverterParameter problem. The DataContext seems to be the RelativeSourceBinding, rather than the DataContext of the item itself… Can we do a RelativeSourceBinding for this too?! :D

  16. Victor says:

    I made some modification to avoid memory leak:

    in RelativeSourceBinding class add property:

    internal DependencyProperty TargetDependencyProperty { get; set; }

    in BindingHelper class add method:

    static void targetElement_Unloaded(object sender, RoutedEventArgs e)
    {
    try
    {
    FrameworkElement targetElement = sender as FrameworkElement;
    targetElement.Unloaded -= new RoutedEventHandler(targetElement_Unloaded);

    RelativeSourceBase bindings = GetBinding(targetElement);

    if (bindings is RelativeSourceBinding)
    {
    // get the binding configuration
    RelativeSourceBinding bindingConfiguration = bindings as RelativeSourceBinding;
    if (bindingConfiguration.TargetDependencyProperty != null)
    targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);
    }
    else if (bindings is BindingList)
    {
    // get the binding configuration
    BindingList list = bindings as BindingList;

    foreach (RelativeSourceBinding bindingConfiguration in list)
    {
    if (bindingConfiguration.TargetDependencyProperty != null)
    targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);
    }
    }
    }
    catch
    {
    }
    }

    change method OnBinding:

    …..
    // attach loading event
    targetElement.Loaded += new RoutedEventHandler(targetElement_Loaded);
    targetElement.Unloaded += new RoutedEventHandler(targetElement_Unloaded);
    …..

    change method CreateBinding:

    // set binding
    if (targetDependencyProperty != null)
    {
    if (bindingConfiguration.TargetDependencyProperty != null && bindingConfiguration.TargetDependencyProperty != targetDependencyProperty)
    targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);

    bindingConfiguration.TargetDependencyProperty = targetDependencyProperty;
    ……….

  17. Tim says:

    Hi, thanks for this usefull class.

    I have a question that should be evident for you, but how would you bind to a dependency property that is in a style ?

    for example :

    I don’t know how to use it in this case, thanks for your help

  18. Tom Puleo says:

    This absolutely rocks. You made my day.

  19. [...] I thought it would be trivial, but due to the absence of FindAncestor construct in Silverlight 4 (It’s coming in Silverlight 5), you need a Beat Kiener’s version of FindAncestor. [...]

  20. Darin says:

    I’m fairly new to Silverlight, but this looks like the solution to a problem I’m having as well. In the ItemTemplate of a ListPicker, I need to be able to refer to a property of the templated listpicker control.

    However, TEMPLATEBINDING doesn’t work from within a datatemplate, only a control template, so I tried a normal BINDING with Relativesource and that doesn’t work either, because I can’t use the FindAncestor to specify how to access the source of the binding (in this case, i need to bind to the HEADER property of the actual ListPicker control itself.

    Any idea if that’s something this concept would allow?

    Thanks!

  21. Nelson says:

    Hi,
    how to asign RelativeSource to DataGridColum’s properties?
    Thanks.

  22. Ben Jones says:

    Hi,

    Are you aware of this binding not working within interaction behaviors at all? I’ve used your code with success except for the following example:

    Whatever I try, I cannot get MyCommand to hit. The F8 command does get triggered but the ViewModel command never does. (I have checked for typos and the binding looks sound)

    Any ideas?

    Many Thanks

  23. Ben Jones says:

    OK, so WP didn’t like me placing in xaml but I tracked down the issue anyway. You require a Framework Element in order to bind the target property once its loaded but a command behavior inherits from DependencyObject, not FrameworkElement so this would never work. Interestlingly, I didn’t even hit the OnBinding method so whether or not this breaks earlier in the chain, I’m not sure. Will have to either make a decision to upgrade to SL5 or find another workaround.

    Many thanks anyway.

  24. Vigemo says:

    Hello,

    Is it possible to have some default values for binding, during design time? ( something like FallbackValue option from classical Binding).

    Thanks!

  25. Nasser Brake says:

    Hi Beat,

    thank you for sharing this behavior! I tried using it with a context-menu to wire the Command property to an ICommand implementation in the ViewModel of the parent UserControl. This doesn’t work because PopUp (being the visual parent of the context-menu) has no visual parent. Or at least not having a parent when Loaded is hit. I tried using LayoutUpdated, but the sender was always null. Could you suggest some other alternative to deal with this issue!

    Thank you for your effort!

  26. Markus says:

    Hi Beat,

    thanks a lot for that solution – you saved my day!

    Regards,
    Markus

  27. Martijn Hoogendoorn says:

    For controls hosted in a Popup, this code should be added in BindingHelper:

    DependencyObject lastObject = currentObject;
    // next parent
    currentObject = VisualTreeHelper.GetParent(currentObject);

    if (currentObject == null && VisualTreeHelper.GetOpenPopups().Count() > 0)
    {
    foreach (Popup popup in VisualTreeHelper.GetOpenPopups())
    {
    if (popup.Child == lastObject)
    {
    currentObject = popup;

    break;
    }
    }
    }

  28. Hello, your articles here RelativeSource Binding with FindAncestor mode in Silverlight | Kiener’s Blog to write well, thanks for sharing

  29. Wout says:

    Hi,

    First many thanks for this addition, works great!
    Is there a possibility for twoway binding?

    Kind regards,

  30. I always used to read article in news papers but now as I am a user of web so from now I am using net for
    articles, thanks to web.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: