Follow Me on Instagram Subscribe via RSS Feed

7th Day of Silverlight : Ancestor Relative Source Binding

December 20, 2011

Here we are at lucky number 7 of our 12 Days of Silverlight series.  I thought I would continue on with the binding theme. I know by now you are singing the 12 days of Silverlight every time you see one of these posts.  I’m tempted to try and put together a YouTube video contest on it.  Hmm…

On the seventh day of Silverlight, the team delivered to me… ancestor relative source binding.

I’ve said it before and I’ll say it again, data binding in Silverlight (and all XAML platforms really) just rocks.  However, when talking about Silverlight, there has been a type of binding that has been missing.  Yep, you guessed it, ancestor relative source binding.  Before I really get into why this is a big deal, let’s take a look at how to actually use it.

The best way to see how to use ancestor binding is to see it in action.  Let’s start a new Silverlight application and set up some simple XAML to look like this:

<Border x:Name="LayoutRoot" 
        BorderBrush="Red"
        BorderThickness="1"
        Width="400"
        Height="400"
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
    <TextBlock Text="Ancestor Binding Rocks!!"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="20"/>
</Border>

If you run the application, you should get a red square with some black text in the middle of it. 

image

Now let’s say that we want the color of our text to be the same as the color of the border.  We can solve this with ancestor binding.  I know you can solve this about a dozen different ways, but those wouldn’t really be good for an ancestor binding demo would they?  So let’s change our XAML to include some relative source binding.

<Border x:Name="LayoutRoot" 
        BorderBrush="Red"
        BorderThickness="1"
        Width="400"
        Height="400"
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
    <TextBlock Text="Ancestor Binding Rocks!!"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Foreground="{Binding BorderBrush,
                RelativeSource={RelativeSource
                    Mode=FindAncestor,
                    AncestorType=Border}}"
                FontSize="20"/>
</Border>

The first thing you should notice is that in the binding, instead of Source or ElementName, we are using RelativeSource.  This means exactly as it sounds, that we will be binding to another element that has some relationship in the Visual Tree to the current element.  Outside of our ancestor binding, you can also use TemplatedParent or Self.  We will not get into them in this post, but you can find out more about them in the MSDN documentation.

The RelativeSource is set to the new RelativeSource object with a Mode property set to ‘FindAncestor’ and the AncestorType property = ‘Border’.  The Mode puts us into our new FindAncestor mode where it will start searching the Visual Tree at the current object and move up the tree until it finds what is it looking for.  In our case, we are looking for a Border control. 

Let’s actually walk thru it.  The binding will start at our TextBlock control.  It will look for the parent of our TextBlock, which is a Border control.  In our case, we only had to go up one step.  But if the parent was a different element, it would continue up the Visual Tree until it found the type we were looking for. 

There is one additional property that we are not using here, AncestorLevel.  This property allows you to specify how many steps up the Visual Tree that you are looking for.  It still needs to have the AncestorType defined so that it knows what object to look for. The RelativeBinding will return the ‘nth’ version of the type defined in AncestorType.  Let’s make some modifications to our XAML and we can see what this looks like.

<Border x:Name="LayoutRoot" 
    BorderBrush="Blue"
    BorderThickness="2"
    Width="440"
    Height="440"
    HorizontalAlignment="Center"
    VerticalAlignment="Center">
    <Border  
    BorderBrush="Green"
    BorderThickness="2"
    Width="420"
    Height="420"
    HorizontalAlignment="Center"
    VerticalAlignment="Center">
        <Border  
    BorderBrush="Red"
    BorderThickness="2"
    Width="400"
    Height="400"
    HorizontalAlignment="Center"
    VerticalAlignment="Center">
            <Grid>
                <TextBlock Text="Ancestor Binding Rocks!!"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Foreground="{Binding BorderBrush,
            RelativeSource={RelativeSource
                Mode=FindAncestor,
                AncestorType=Border,AncestorLevel=2}}"
            FontSize="20"/>
            </Grid>

        </Border>
    </Border>
</Border>

I added a three extra elements to our our example, 2 Border’s and a Grid.  The Grid’s purpose is to demonstrate that the RelativeBinding will skip over types not defined in the AncestorType property.  We have set our AncestorLevel to ‘2’.  As the RelativeBinding moves up the Visual Tree, it skips past the Grid control, then encounters the ‘red’ Border as 1, then finds the 2nd Border and returns it.  You can remove the added Grid control just to see that it has no bearing in the search.  Your results should look like this:

image

Now that we have seen how to use it, let’s talk about why we should use it.  I have one word for you, reusability.  As I mentioned earlier, there are different ways that you can accomplish this same thing.  The primary way is by using ElementName in your binding.  The problem with using ElementName is that you are forcing your code to look for hard coded names.  For instance, what if you created a DataTemplate for a ComboBox that needed access to the ComboBox itself.  If you used ElementName in your code, then you could not reuse the DataTemplate in other ComboBoxes without making a change to the ElementName.  

I can hear the doubts floating across cyber space.  So let’s take a look at an example that you might come across.  One of the coolest things I like about Silverlight, I know I say that frequently but there is just so much to like, is the ability to create very visual list boxes and combo boxes.  This feature alone has done a tremendous amount for UI advancement.

Even with this advancement, you don’t always want large UI elements displayed for a ComboBox.  So what if we wanted to display a simple text string for the selected item of a ComboBox when the ComboBox is closed, but wanted to show a richer interface when the user is selected an item?  Basically adding a dual mode for the display.

To begin with, this example is going to need a ValueConverter that changes a string of a color to a SolidColorBrush.  So a string with a value of ‘red’ would return a SolidColorBrush with the Color property set to Colors.Red.  Here is what it looks like:

public class TextToSolidColorConverter : IValueConverter
{

    public object Convert(object value, 
                        Type targetType, 
                        object parameter, 
                        CultureInfo culture)
    {
        var xaml = "<SolidColorBrush " +
             "xmlns='http://schemas.microsoft.com/client/2007' " +
             "Color=\"" + value.ToString() + "\"/>";
        return XamlReader.Load(xaml);
    }

    public object ConvertBack(object value, 
                        Type targetType, 
                        object parameter, 
                        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Next we will add a DataTemplate to our MainPage resources that we will use for a couple of ComboBox controls in just a minute.  We also need to add an instance of our ValueConverter that we just created.

<UserControl.Resources>
    <local:TextToSolidColorConverter x:Key="colorConv"/>
    <DataTemplate x:Key="cbItem">
        <StackPanel Orientation="Horizontal">
            <Ellipse Width="25"
                        Height="25"
                        Margin="5,5,20,5"
                        Fill="{Binding .,
                Converter={StaticResource colorConv}}"
                />
            <TextBlock
                VerticalAlignment="Center"
                Text="{Binding .}"/>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

We can add two ComboBox controls to a StackPanel that will also contain the original TextBlock, simply to make everything simple for demo purposes.  The modified XAML looks like this:

<Grid>
    <StackPanel>
        <TextBlock Text="Ancestor Binding Rocks!!"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Foreground="{Binding BorderBrush,
        RelativeSource={RelativeSource
        Mode=FindAncestor,
        AncestorType=Border,AncestorLevel=2}}"
        FontSize="20"/>
        <ComboBox x:Name="cb2"
        Margin="0,20,0,0"
        Width="200"
        Height="Auto"
        ItemTemplate="{StaticResource cbItem}"
        HorizontalAlignment="Center"
        />
        <ComboBox x:Name="cb1"
        Margin="0,20,0,0"
        Width="200"
        Height="Auto"
        ItemTemplate="{StaticResource cbItem}"
        HorizontalAlignment="Center"
        />
    </StackPanel>

The last thing we need to do is get some data to these new ComboBox controls.  We will do that be assigning a List<string> of a few colors in our constructor in the code behind.

public MainPage()
{
    InitializeComponent();

    var colors = new List<string>() {"Red", "Green", "Blue"};
    cb1.ItemsSource = colors;
    cb2.ItemsSource = colors;
}

If we run the new project, we will get two new ComboBox controls.  Once you expand one you will see our new data template in action.

image

So this looks great and is very informative to the user.  However, what if we simply wanted to show the text of the color when the ComboBox is collapsed?  This makes a great case for using our new ancestor binding.  For this example, we need to add one more ValueConverter.  This converter will change a bool to Visibility, where true returns Visibility.Visible.

public class BoolToVisibilityConverter : IValueConverter
{

    public object Convert(object value,
                        Type targetType,
                        object parameter,
                        CultureInfo culture)
    {
        if(value is bool)
        {
            return (bool)value ? Visibility.Visible
                               : Visibility.Collapsed;
        }

        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, 
                        Type targetType, 
                        object parameter, 
                        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

With our new ValueConverter ready, we can modify our DataTemplate.  We are going to set the Visibilty of our Ellipse based off of the IsDropDownOpen property of the ComboBox.  So if the drop down is opened we will show the Ellipse and the text.  If not, then just the text.  Our updated resources section looks like this:

<UserControl.Resources>
    <local:TextToSolidColorConverter x:Key="colorConv"/>
    <local:BoolToVisibilityConverter x:Key="boolConv"/>
    <DataTemplate x:Key="cbItem">
        <StackPanel Orientation="Horizontal">
            <Ellipse Width="25"
                Height="25"
                Margin="5,5,20,5"
                Fill="{Binding .,
                Converter={StaticResource colorConv}}"
                Visibility="{Binding IsDropDownOpen,
                RelativeSource={RelativeSource Mode=FindAncestor,
                AncestorType=ComboBox}, 
                Converter={StaticResource boolConv}}"
        />
            <TextBlock
        VerticalAlignment="Center"
        Text="{Binding .}"/>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

Now if we run our example, your application should look like this:

image

As you can see, the ComboBox item display changes whether the drop down is opened or not.  One thing to note, if we had used ElementName instead of ancestor binding, then we would not have been able to use the same DataTemplate for both of the ComboBox controls.

As always, demos are just that, demos.  Using ancestor binding in real-world projects has a lot more potential.  You can find the source to this post here : DaysOfSilverlight_AncestorBinding.zip

Work has already begun on the next post in the 12 Days of Silverlight series.  So we will see you back soon.

Series Navigation<< 6th Day of Silverlight : Binding in Style Setters8th Day of Silverlight : Vector Printing >>

Leave a Reply