May 07, 2006

How to change the way a data item is displayed when the user clicks on it

Today I will explain how the RelativeSource FindAncestor works (new in Feb CTP), and how you can use it to change the way a data item is displayed when the user clicks on it.

You should use a FindAncestor binding when you want the source of your binding to be an element somewhere up in the tree from the target element. When binding using FindAncestor, the binding engine walks up first the visual tree (when used inside a template), then the logical tree, until it finds an element with the type specified in AncestorType. There is also an AncestorLevel property that allows you to skip elements of that type. For example, if you want to bind to the third element of type ListBoxItem when going up the tree, you would set AncestorType={x:Type ListBoxItem} and AncestorLevel=3, which skips the first two ListBoxItems.

But couldn’t we use an ElementName binding to achieve the same result? Certainly, if we have control over the creation of the source item and are able to set a Name on it. In many scenarios, however, Avalon is responsible for creating elements automatically for us, and in this case we can not set their Names. For example, if we data bind a ListBox to a list of data items, Avalon generates the ListBoxItem elements that wrap those items behind the scenes.

The following markup shows how to template a data item such that when the corresponding ListBoxItem is selected, the text becomes bold. In this case, I want to find the first ListBoxItem up in the tree from the TextBlock displaying the current data item. I then want to set FontWeight=Bold on the TextBlock if the ListBoxItem’s IsSelected property is true. The only way to bind to that ListBoxItem from within the DataTemplate is by using a FindAncestor binding, with the syntax below:

    <Window.Resources>
        <local:GreekGods x:Key="source" />

        <DataTemplate x:Key="dataTemplate">
            <TextBlock Text="{Binding Path=Name}" Margin="5,2,5,2" x:Name="tb"/>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}, Path=IsSelected}" Value="True">
                    <Setter Property="FontWeight" Value="Bold" TargetName="tb" />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>

    <ListBox ItemsSource="{StaticResource source}" ItemTemplate="{StaticResource dataTemplate}" Width="100"/>

Here is a screenshot of this application:

One common scenario is for people to want control over the foreground and background color of the selected item, so I’ll go over some thoughts on this.

You can control the background color of the TextBlock with this method, but this background only takes enough space to display the text. The typical scenario is for people to want control over the ListBoxItem’s background, which can not be changed from within the DataTemplate. If this is your scenario, you have two options:

If you want to change the background of all selected items in your window, you can add the following to the Window’s Resources:

    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="IndianRed" />

If you want the change to have effect only for this particular ListBox, consider using Sparkle to get the default ControlTemplate for the ListBoxItem. You can then replace the background color in the trigger that’s activated when ListBoxItem.IsSelected=true. Keep in mind that if you hard code the color (instead of using SystemColors keys), it will be the same across all themes, which is usually not what you want.

You can control the TextBlock’s foreground color with this method – simply add the following Setter to the DataTrigger:

    <Setter Property="Foreground" Value="IndianRed" TargetName="tb" />

If you already have a ControlTemplate for the ListBoxItem for other reasons, however, you can control the Foreground property there instead. This can be done because the Foreground color of the ListBoxItem is propagated to the TextBlock (Foreground is an inherited DP).

Another alternative is to use a Style for the ListBoxItem in the following way:

    <Style TargetType="{x:Type ListBoxItem}">
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Foreground" Value="IndianRed" />
            </Trigger>
        </Style.Triggers>
    </Style>

Here you can find the VS project with this sample code. This works with February CTP WPF bits.

2 Comments
  1. Pierre

    Hi Beatriz,
    I’ve been downloading all your examples! great stuff! one thing bugs me though. I’m trying to play with the ObjectDataProvider and the XMLDataProvider in CODE….not easy! no docs of course, but that’s the fun of beta. Seriously I can’t even instantiate them properly (believe me, I’ve tried a number of stuff). Are we condemned to instantiate the object in code and set it as datacontext and/or register it as resource? or Instantiate an “empty” XmlDataProvider and then filling it up? BTW, read your article about ODP, great stuff!
    Pierre.

    • Bea

      Hi Pierre,

      I’m glad you liked the ODP article. I’m never sure if people have the patience to read my long articles. I get a little too enthusiastic about this stuff sometimes :)

      I instantiated an ObjectDataProvider in code in my last blog post because of your question. If there is anything else you can’t find documentation for, I’d like to hear about it. All my posts so far are based on people’s questions, and I want to keep it that way.

      Thanks,
      Bea

Comments are closed.