#1,187 – Using an ItemContainerStyle to Change Items in an ItemsControl

One common use when defining a new ItemContainerStyle for an ItemsControl is to do something special with either the currently selected item or the item that the mouse is currently over.  The default template used as an ItemContainerStyle for a ListBox, for example, changes the background and border for both the selected item and the item that the mouse is currently over.

Below is an example where we change the FontWeight for the currently selected item (to SemiBold) and make the item larger, using a transform.  This method uses a trigger to change several properties.

        <Style x:Key="FocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
        <SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
        <SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>
        
        <Style x:Key="lbDefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="Padding" Value="4,1"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
                                <Setter Property="LayoutTransform">
                                    <Setter.Value>
                                        <ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="FontWeight" Value="SemiBold"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="False"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="Selector.IsSelectionActive" Value="True"/>
                                    <Condition Property="IsSelected" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
                                <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

The resulting behavior looks like this:
1187-001

#1,185 – ItemsControl Customization Summary

You can can customize an ItemsControl, or any of the controls that derive from it (ComboBox, DataGrid, ListBoxTabControl, TreeView et al), in a number of ways.  The various customization mechanisms are summarized below.

  • Set Style to apply a set of property values for the main control
  • Set Template to change the control template of the control.
    • This changing the highest level of the control (e.g. for a ListBox, a Border wrapping a ScrollViewer, in turn wrapping an ItemsPresenter)
  • Set ItemsPanel to change the panel used to lay out the individual items
    • E.g. User horizontally oriented StackPanel to lay out ListBox items horizontally, rather than vertically
  • Set ItemContainerStyle to set properties that apply to the container for each item
    • E.g. Properties that apply to each ListBoxItem within a ListBox
  • Set ItemTemplate to change the data template used to render each item in the list
    • E.g. Create a custom layout for each item in a ListBox

 

#1,017 – Scaling Items in a List Using a Slider

Below is an example of using a Slider control to scale a bunch of images in an ItemsControl.  The images are displayed within a WrapPanel.  As they are resized, the WrapPanel automatically updates the layout, so that the number of rows needed to display the images changes.

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ScrollViewer>
            <ItemsControl ItemsSource="{Binding ActorList}" Margin="20">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding Image}" Height="100">
                            <Image.LayoutTransform>
                                <ScaleTransform ScaleX="{Binding Value, ElementName=sliScale}"
                                                ScaleY="{Binding Value, ElementName=sliScale}"/>
                            </Image.LayoutTransform>
                        </Image>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </ScrollViewer>
        <Slider Name="sliScale" Grid.Row="1" Margin="10,5"
                Minimum="0.1" Maximum="3"
                Value="1.0"/>
    </Grid>

1017-001

 

1017-002

1017-003

#1,016 – Displaying a Collection of Items in a WrapPanel

You can use data binding to bind a collection of items to a WrapPanel by using the WrapPanel as the ItemsPanel for a simple ItemsControl.  (You could do the same thing with a ListBox).

In the example below, we bind to a collection of Actor objects and set the DataTemplate of our ItemsControl to just display the image of each actor.  The ItemsControl will resize to fit the containing window (via the StackPanel) and the WrapPanel will manage changing the layout of the images as the window size changes.

    <ItemsControl ItemsSource="{Binding ActorList}" Margin="20">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Image Source="{Binding Image}" Height="100"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

1016-001
1016-002
1016-003

#1,002 – Using Larger Values for AlternationCount in an ItemsControl

The AlternationCount and AlternationIndex properties of controls inheriting from ItemsControl allow setting property values that alternate across the collection of items.

An alternation count of two allows you to apply a style that affects every other item in a list.  Higher values allow a longer sequence of items before a particular pattern repeats.

In the example below, we set the Background color of each item differently.  The pattern repeats after every fourth item because the AlternationCount is set to 4.

    <Window.Resources>
        <AlternationConverter x:Key="altconvBackground">
            <SolidColorBrush Color="Azure"/>
            <SolidColorBrush Color="LightGreen"/>
            <SolidColorBrush Color="LightPink"/>
            <SolidColorBrush Color="Thistle"/>
        </AlternationConverter>
    </Window.Resources>

    <Grid Margin="10" >
        <ListBox ItemsSource="{Binding ActorList}" Width="200"
                 AlternationCount="4">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Background"
                            Value="{Binding RelativeSource={RelativeSource Self}, Path=(ItemsControl.AlternationIndex), Converter={StaticResource altconvBackground}}"/>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>

1002-001

#1,001 – Setting an Alternating Background Color in an ItemsControl

The AlternationCount and AlternationIndex properties of controls inheriting from ItemsControl allow setting property values that alternate across the collection of items.

The AlternationCount property dictates how many unique styles you alternate between.  The most common value is 2, indicating that every other item has a particular style.  In the example below, we use these properties to set up an alternating background color.

    <Window.Resources>
        <AlternationConverter x:Key="altconvBackground">
            <SolidColorBrush Color="AliceBlue"/>
            <SolidColorBrush Color="White"/>
        </AlternationConverter>
    </Window.Resources>

    <Grid Margin="10" >
        <ListBox ItemsSource="{Binding ActorList}" Width="200"
                 AlternationCount="2">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Background"
                            Value="{Binding RelativeSource={RelativeSource Self}, Path=(ItemsControl.AlternationIndex), Converter={StaticResource altconvBackground}}"/>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>

We set an ItemContainerStyle because we want to set a property for a given ListBoxItem.  We then set the Background of each item to derive its value from the AlternationIndex property of the item.  The AlternationConverter is used to convert this index, 0 or 1, into a SolidColorBrush.

#977 – DisplayMemberPath Indicates Property to Use for Displaying Bound Items

When you use data binding to bind an ItemsControl to a collection, there are several ways to control how each item in the list is rendered:

  • Rely on the ToString method of the bound item to generate a string to be displayed
  • Use a data template to create a more complex rendering of the item
  • Use the DisplayMemberPath property to indicate which property should be used in generating a string

DisplayMemberPath can be set to the name of a property (of type string) on the bound object.  The value of that property is then used as the displayed value of each item in the list.

Below, the KnownFor property of an Actor is used to generate the string in the ListBox.

        <ListBox Margin="15" Width="200" Height="150"
                 ItemsSource="{Binding ActorList}"
                 DisplayMemberPath="KnownFor"
                 SelectedItem="{Binding SelectedActor}"/>
        <TextBlock Text="{Binding SelectedActor.FullName}"
                   HorizontalAlignment="Center" Margin="0,5"/>

977-001