91
votes

I need to change the DataTemplate for items in a ListBox depending on whether the item is selected or not (displaying different/more information when selected).

I don't get a GotFocus/LostFocus event on the top-most element in the DataTemplate (a StackPanel) when clicking the ListBox item in question (only through tabbing), and I'm out of ideas.

3

3 Answers

187
votes

The easiest way to do this is to supply a template for the "ItemContainerStyle" and NOT the "ItemTemplate" property. In the code below I create 2 data templates: one for the "unselected" and one for the "selected" states. I then create a template for the "ItemContainerStyle" which is the actual "ListBoxItem" that contains the item. I set the default "ContentTemplate" to the "Unselected" state, and then supply a trigger that swaps out the template when the "IsSelected" property is true. (Note: I am setting the "ItemsSource" property in the code behind to a list of strings for simplicity)

<Window.Resources>

<DataTemplate x:Key="ItemTemplate">
    <TextBlock Text="{Binding}" Foreground="Red" />
</DataTemplate>

<DataTemplate x:Key="SelectedTemplate">
    <TextBlock Text="{Binding}" Foreground="White" />
</DataTemplate>

<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
    <Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
    <Style.Triggers>
        <Trigger Property="IsSelected" Value="True">
            <Setter Property="ContentTemplate" Value="{StaticResource SelectedTemplate}" />
        </Trigger>
    </Style.Triggers>
</Style>

</Window.Resources>
<ListBox x:Name="lstItems" ItemContainerStyle="{StaticResource ContainerStyle}" />
10
votes

To set the style when the item is selected or not all you need to do is to retrieve the ListBoxItem parent in your <DataTemplate> and trigger style changes when its IsSelected changes. For example the code below will create a TextBlock with default Foreground color green. Now if the item gets selected the font will turn red and when the mouse is over the item will turn yellow. That way you don't need to specify separate data templates as suggested in other answers for every state you'd like to slightly change.

<DataTemplate x:Key="SimpleDataTemplate">
    <TextBlock Text="{Binding}">
        <TextBlock.Style>
            <Style>
                <Setter Property="TextBlock.Foreground" Value="Green"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={
                        RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem }}}"
                                 Value="True">
                        <Setter Property="TextBlock.Foreground" Value="Red"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=IsMouseOver, RelativeSource={
                        RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem }}}"
                                 Value="True">
                        <Setter Property="TextBlock.Foreground" Value="Yellow"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</DataTemplate>
6
votes

It should also be noted, that the stackpanel isn't focuable, so it's never going to get focus (set Focusable=True if you /really/ want it focused). However, the key to remember in scenarios like this is that the Stackpanel is child of the TreeViewItem, which is the ItemContainer in this case. As Micah suggests, tweaking the itemcontainerstyle is a good approach.

You could probably do it using DataTemplates, and things such as datatriggers which would use the RelativeSouce markup extension to look for the listviewitem