Alternating row background color in WPF ListView

In yesterday’s post you’ll notice that the ListView control displays using different colors for alternating rows. I implemented this effect by using a style selector. A style selector is basically a class that inherits from StyleSelector and overrides the SelectStyle method. Here’s my implementation.

public class ListViewItemStyleSelector : StyleSelector
{
    public override Style SelectStyle(object item, DependencyObject container)
    {
        ListView listView = ItemsControl.ItemsControlFromItemContainer(container) as ListView;

        Style st;
        int index = listView.ItemContainerGenerator.IndexFromContainer(container);
        if (index % 2 == 0)
        {
            st = (Style)listView.FindResource("ListViewItemRow2");
        }
        else
        {
            st = (Style)listView.FindResource("ListViewItemRow1");
        }
        return st;
    }
}

Notice that the SelectStyle method first determines the index of the item being rendered. It uses this index to select a Style instance from the application’s static resources. I’ll get to these resources shortly.

Next I create an instance of this class in the application resource dictionary.

<controls:ListViewItemStyleSelector x:Key="ListViewItemStyleSelector"/>

The ListView property ItemContainerStyleSelector must be set to this or any other instance of my ListViewItemStyleSelector class. Actually ItemContainerStyleSelector is a property of ItemsControl, so this technique can be used on any ItemsControl, such as ListBox, not just the ListView used here.

<Style x:Key="{x:Type ListView}" TargetType="{x:Type ListView}">
  <Setter Property="ItemContainerStyleSelector" Value="{StaticResource ListViewItemStyleSelector}"/>
…
</Style>

Finally the two styles referenced by SelectStyle must be inserted into the application resource dictionary.

<Style x:Key="ListViewItemBase" TargetType="{x:Type ListViewItem}">
  <Setter Property="SnapsToDevicePixels" Value="true"/>
  <Setter Property="OverridesDefaultStyle" Value="true"/>
  <Setter Property="Foreground" Value="{StaticResource DarkForegroundBrush}"/>
  <Setter Property="Template" Value="{StaticResource ListViewItemTemplate}"/>
  <Setter Property="Margin" Value="2,0,2,1"/>
</Style>
  
<Style x:Key="ListViewItemRow1" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemBase}">
    <Setter Property="Background" Value="{StaticResource ListViewLine1Brush}"/>
</Style>

<Style x:Key="ListViewItemRow2" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemBase}">
  <Setter Property="Background" Value="{StaticResource ListViewLine2Brush}"/>
</Style>

Here I factored all of the common property settings into a base instance of Style named ListViewItemBase. The two Style instances used to render the ListView rows, ListViewItemRow1 and ListViewItemRow2, inherit these base settings using the BasedOn property: BasedOn=”{StaticResource ListViewItemBase}”.

This approach works great when the collection bound to ListView.ItemsSource is static, as it is in my test window. Everything breaks down, however, when items are added or deleted to the underlying collection.

I created a new Visual Studio project to isolate the ListView. In this project I bind ListView.ItemsSource to an ObservableCollection instead of specifying a collection of ListViewItems in my main window XAML as I did in the last example. Here’s the ListView in the new test window.

<ListView x:Name="_list" Margin="8" Height="120" Width="240" ItemsSource="{Binding}">
    <ListView.View>
        <GridView AllowsColumnReorder="true">
            <GridViewColumn DisplayMemberBinding="{Binding}" Header="Content" Width="100"/>
            <GridViewColumn DisplayMemberBinding="{Binding Path=Length}" Header="Length" Width="100"/>
        </GridView>
    </ListView.View>
</ListView>

And the relevant C# code in the code behind file.

private ObservableCollection<string> _items = new ObservableCollection<string>();
public Window1()
{
    _items.Add("First Item");
    _items.Add("Second Item");
    _items.Add("Third Item");
    _items.Add("Fourth Item");
    _items.Add("Fifth Item");
    _items.Add("Sixth Item");
    _items.Add("Seventh Item");
    _items.Add("Eighth Item");
    InitializeComponent();
    DataContext = _items;
}

I also added some buttons and text boxes to the test window to facilitate adding and deleting of collection items. Here are the relevant Button.Click event handlers.

private void DelButton_Click(object sender, RoutedEventArgs e)
{
    int n = 0;
    if (Int32.TryParse(_delItemNum.Text, out n))
    {
        if (n >= 1 && n <= _items.Count)
        {
            _items.RemoveAt(n-1);
        }
    }
}

private void AddButton_Click(object sender, RoutedEventArgs e)
{
    int n = 0;
    if (Int32.TryParse(_addItemNum.Text, out n) && String.IsNullOrEmpty(_addItemText.Text))
    {
        if (n >= 1 && n <= _items.Count)
        {
            _items.Insert(n - 1, _addItemText.Text);
        }
        else
        {
            _items.Add(_addItemText.Text);
        }
    }
}&#91;/sourcecode&#93;

The screenshot below shows what happens after deleting the third collection item.  Notice that the individual row background colors were not reset after deleting the item.  “Second Item” and “Fourth Item” are both the darker shade of gray.

<img src="https://patconroy.files.wordpress.com/2009/01/badlistview.jpg" alt="badlistview" title="badlistview" width="375" height="375" class="alignnone size-full wp-image-51" />

It turns out that once the item style is selected it will never be modified regardless of how the underlying data collection changes.  A workaround for this is to refresh the ListView’s collection view.  This can be done just after adding or deleting an item.

_items.RemoveAt(n-1);
System.ComponentModel.ICollectionView dataView =
    CollectionViewSource.GetDefaultView(_list.ItemsSource);
dataView.Refresh();

A better place to put this is in an event handler for ObservableCollection.CollectionChanged.

private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    System.ComponentModel.ICollectionView dataView =
        CollectionViewSource.GetDefaultView(_list.ItemsSource);
    dataView.Refresh();
}

Now the individual row backgrounds get reset correctly after items are added or removed from the data collection, and the alternating background color pattern is maintained.

The technique described here works for all ItemsControls in all versions of .NET that include WPF, 3.0, 3.1 and 3.1 SP1. A new feature was introduced in 3.5 SP1 that simplifies this a great deal. I’ll describe that in my next post. Stay tuned.

Advertisements

1 Response to “Alternating row background color in WPF ListView”



  1. 1 More on ListView styles « Discovering .NET Trackback on January 5, 2009 at 11:19 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




January 2009
M T W T F S S
« Dec   Feb »
 1234
567891011
12131415161718
19202122232425
262728293031  
I am a part of all that I have met;
Yet all exprience is an arch whitherthro'
Gleams that untravell'd world, whose margin fades
For ever and for ever when I move.
How dull it is to pause, to make an end,
To rust unburnish'd, not to shine in use!
Alfred, Lord Tennyson

%d bloggers like this: