32

I have seen some articles that show how to use AlternationIndex with ListBoxes or ListViews, but I had spent a few hours trying to get alternating background colors on the base ItemsControl class and nothing seems to work. All ListBox samples I saw use ListBoxItem as the target type for the style that sets the background based onAlternationIndex - like this one from MSDN:

<Grid>
    <Grid.Resources>
        <Style x:Key="alternatingWithTriggers" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Background" Value="Blue"/>
            <Setter Property="Foreground" Value="White"/>
            <Style.Triggers>
                <Trigger Property="ListBox.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="CornflowerBlue"/>
                    <Setter Property="Foreground" Value="Black"/>
                </Trigger>
                <Trigger Property="ListBox.AlternationIndex" Value="2">
                    <Setter Property="Background" Value="LightBlue"/>
                    <Setter Property="Foreground" Value="Navy"/>
                </Trigger>
            </Style.Triggers>
        </Style>

    </Grid.Resources>
    <ListBox AlternationCount="3" ItemsSource="{StaticResource data}" 
             ItemContainerStyle="{StaticResource alternatingWithTriggers}">
    </ListBox>
</Grid>

I want to use the ItemsControl because I do not want the selection functionality and I think restyling a ListBox to hide it might not be the best choice.

This is one of the things I was trying:

<DataTemplate DataType="{x:Type vm:ObservableCollectionItem}">
    <Grid>
        <!-- some content here -->
    </Grid>
</DataTemplate>

<!-- ... -->

<ItemsControl
    ItemsSource="{Binding ObservableCollectionItems}"
    AlternationCount="2"
>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Style.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Grid.Background" Value="Red"></Setter>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Grid.Background" Value="Blue"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

The problem I saw was that the visual tree has a list of ContentPresenters that have ItemsControl.AlternationIndex alternate between 0 and 1, but the Grid in each ContentPresenter has ItemsControl.AlternationIndex set to 0.

There is probably something obvious I am missing...

Sheridan
  • 64,785
  • 21
  • 128
  • 175
Filip Skakun
  • 31,347
  • 6
  • 71
  • 99

5 Answers5

52

The ItemContainerStyle is applied to the elements generated by the ItemsControl: ContentPresenter. The ContentPresenter will in turn contain whatever you put in your ItemTemplate. In the case of a ListBox, the ItemContainerStyle is applied to the generated ListBoxItem.

The AlternationCount is, based on what you posted, only available on these generated items. You cannot use the ItemContainerStyle to set the Grid's background, because the Grid is unknown to that Style.

The following would be ideal, but unfortunately ContentPresenter has no background property. It would work for a ListBox (with ListBoxItems) however.

<ItemsControl
    ItemsSource="{Binding ObservableCollectionItems}"
    AlternationCount="2">
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Style.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Background" Value="Red"></Setter>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="Blue"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

So you end up writing a style for the grid which binds to the AlternationIndex of your parent ContentPresenter.

<DataTemplate DataType="{x:Type vm:ObservableCollectionItem}">
    <Grid>
        <Grid.Style>
            <Style TargetType="Grid">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
                        <Setter Property="Background" Value="Red"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
                        <Setter Property="Background" Value="Blue"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Grid.Style>
    </Grid>
</DataTemplate>
Bubblewrap
  • 6,778
  • 31
  • 30
  • It worked! Thanks! I am wondering if this lookup binding would scale on big ItemControls, but fortunately mine is not the case. – Filip Skakun Aug 25 '10 at 17:54
  • 1
    The second example works, the first does not, as the ContentPresenter does *not* have a property `Background`. –  Jul 07 '11 at 18:11
  • I love it when I Google something and find the exact answer I'm looking for. Thanks for posting! – cplotts Jan 24 '13 at 23:44
  • I must add AlternationCount="2" to ItemsControl to make it work. Anyone know why? –  Nov 28 '16 at 05:00
32

hm... After about 2 hours of playing around, I finally found the solution that simply works:

<ItemsControl ItemsSource="{Binding}" AlternationCount="2">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Background="Transparent" x:Name="__PART_GRID"></Grid>
            <DataTemplate.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter TargetName="__PART_GRID" Property="Background" Value="Red"/>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter TargetName="__PART_GRID" Property="Background" Value="Blue"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

I hope this answer helps others to save some time.

Grigory Zhadko
  • 860
  • 1
  • 12
  • 22
Nachbars Lumpi
  • 2,457
  • 1
  • 18
  • 13
  • This solution is **much** clearer than one suggested by @Bubblewrap so ... +1! Thanks! – AxelEckenberger Mar 24 '14 at 11:35
  • Definitely a much cleaner solution, IMO. It might be worth noting, the named component MUST come before the triggers or it will throw an error. (Though, the error message is pretty clear about what the problem is) – Herohtar Jul 28 '17 at 19:15
5

Or, as I found on another post, and it works great for me... You can simply use a binding...

{Binding
    RelativeSource={RelativeSource Mode=TemplatedParent}, 
    Path=(ItemsControl.AlternationIndex)}

NB: remember to add AlterationCount="100" on your ItemsControl

Becky York
  • 121
  • 1
  • 3
1

I don't know how any of the prior answers are legit. I couldn't make any of them work (didn't try Jacobi's though). Anyways, I found the path to enlightenment here: http://www.dotnetcurry.com/wpf/1211/wpf-items-control-advanced-topic, which lead me to adding the following in the xaml.cs code-behind:

public sealed class CustomItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ContentControl();
    }
}

and this in the xaml itself

    <local:CustomItemsControl AlternationCount="2" 
          ItemsSource="{Binding Cells, Mode=OneWay}">
        <local:CustomItemsControl.ItemContainerStyle>
            <Style TargetType="ContentControl">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ContentControl">
                            <Border Background="{TemplateBinding Background}">
                                <ContentPresenter/>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>

                <Style.Triggers>
                    <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                        <Setter Property="Background" Value="WhiteSmoke"/>
                    </Trigger>
                    <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                        <Setter Property="Background" Value="LightGray"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </local:CustomItemsControl.ItemContainerStyle>
    </local:CustomItemsControl>

This was so damn hard to find a working solution to that I'm actually angry

Sean
  • 2,020
  • 2
  • 11
  • 15
0

If you don't want to use the DataTemplate approach, you can create a custom control that uses a ContentControl as the item container, therefore allowing you to specify a background color.

Class:

public class ItemsControlAlternating : ItemsControl
{
    static ItemsControlAlternating()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemsControlAlternating),
                 new FrameworkPropertyMetadata(typeof(ItemsControlAlternating)));
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ContentControl();
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is ContentControl;
    }
}

Resource Dictionary:

<Style TargetType="{x:Type c:ItemsControlAlternating}">
   <Setter Property="AlternationCount" Value="2"/>
   <Setter Property="Template">
       <Setter.Value>
           <ControlTemplate TargetType="{x:Type c:ItemsControlAlternating}">
               <ItemsPresenter/>
           </ControlTemplate>
       </Setter.Value>
   </Setter>
   <Setter Property="ItemContainerStyle">
       <Setter.Value>
           <Style TargetType="{x:Type ContentControl}">
               <Setter Property="Template">
                   <Setter.Value>
                       <ControlTemplate TargetType="{x:Type ContentControl}">
                           <Border Background="{TemplateBinding Background}">
                               <ContentPresenter/>
                           </Border>
                       </ControlTemplate>
                   </Setter.Value>
               </Setter>
               <Style.Triggers>
                   <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                       <Setter Property="Background" Value="Gray"/>
                   </Trigger>
                   <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                       <Setter Property="Background" Value="White"/>
                   </Trigger>
               </Style.Triggers>
           </Style>
       </Setter.Value>
   </Setter>
</Style>
Matt Jacobi
  • 805
  • 7
  • 16