Super Fast alternating row colors in a ListBox

One of the applications we are building required a ListBox control that displayed a bunch of contacts. We also wanted to make the rows in the ListBox have alternating row colors, just like the figure below:

image

Our first guess was to use an ItemTemplateSelector. Inside the selector we would check the index of the item and furnish the appropriate DataTemplate. But this required duplication of the DataTemplate and unnecessarily cluttered the ResourceDictionary. There had to be a simpler way.

After some brain-storming with David (my team mate) we figured out a better way. The idea was to use a tiled DrawingBrush.

A tiled DrawingBrush

Each row in our ListBox was having a fixed Heightof 30. We decided to leverage that fact while building the DrawingBrush. To understand the technique lets get a quick refresher on the way a TileBrush works with its TileMode = “Tile”. Here is a simple DrawingBrush (taken from MSDN Docs) and the result of painting a Rectangle with TileMode = “Tile”

image

If you look at our ListBox above, the graphic we want to tile should look like so:

image

In XAML, this would translate to:

 1 <DrawingBrush Viewbox="0,0,10,60"
 2               ViewboxUnits="Absolute"
 3               TileMode="Tile"
 4               Viewport="0,0,10,60"
 5               ViewportUnits="Absolute">
 6     <DrawingBrush.Drawing>
 7         <DrawingGroup>
 8             <GeometryDrawing Brush="#19f39611">
 9                 <GeometryDrawing.Geometry>
10                     <RectangleGeometry RadiusX="0"
11                                        RadiusY="0"
12                                        Rect="0,0,10,30"/>
13                 </GeometryDrawing.Geometry>
14             </GeometryDrawing>
15 
16             <GeometryDrawing Brush="#19000000">
17                 <GeometryDrawing.Geometry>
18                     <RectangleGeometry RadiusX="0"
19                                        RadiusY="0"
20                                        Rect="0,30,10,30"/>
21                 </GeometryDrawing.Geometry>
22             </GeometryDrawing>
23         </DrawingGroup>
24     </DrawingBrush.Drawing>
25 </DrawingBrush>	
Note the properties ViewBox, ViewBoxUnits, Viewport and ViewportUnits. Essentially the DrawingBrush consists of two Rectangles of Height=30 placed one over other.

Using it with ListBox

Here is the XAML for the ListBox with the alternating background. You can paste it directly into XamlPad or Kaxaml. It works just as you expect, scrolls well and is super-fast ;).

 1 <Page
 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 4     <Page.Resources>
 5         <DrawingBrush x:Key="AlternatingRowBackground"
 6                       Viewbox="0,0,10,60"
 7                       ViewboxUnits="Absolute"
 8                       TileMode="Tile"
 9                       Viewport="0,0,10,60"
10                       ViewportUnits="Absolute">
11             <DrawingBrush.Drawing>
12                 <DrawingGroup>
13                     <GeometryDrawing Brush="#19f39611">
14                         <GeometryDrawing.Geometry>
15                             <RectangleGeometry RadiusX="0"
16                                                RadiusY="0"
17                                                Rect="0,0,10,30"/>
18                         </GeometryDrawing.Geometry>
19                     </GeometryDrawing>
20 
21                     <GeometryDrawing Brush="#19000000">
22                         <GeometryDrawing.Geometry>
23                             <RectangleGeometry RadiusX="0"
24                                                RadiusY="0"
25                                                Rect="0,30,10,30"/>
26                         </GeometryDrawing.Geometry>
27                     </GeometryDrawing>
28                 </DrawingGroup>
29             </DrawingBrush.Drawing>
30         </DrawingBrush>
31 
32         <ControlTemplate x:Key="ICTemplate">
33             <ScrollViewer>
34                 <Grid Background="{StaticResource AlternatingRowBackground}">
35                     <ItemsPresenter/>
36                 </Grid>
37 
38             </ScrollViewer>
39         </ControlTemplate>
40     </Page.Resources>
41     <Grid>
42 
43         <ListBox Template="{StaticResource ICTemplate}">
44             <Grid Height="30">
45                 <TextBlock Text="Contact - 1"
46                            VerticalAlignment="Center"/>
47             </Grid>
48             <Grid Height="30">
49                 <TextBlock Text="Contact - 2"
50                            VerticalAlignment="Center"/>
51             </Grid>
52             <Grid Height="30">
53                 <TextBlock Text="Contact - 3"
54                            VerticalAlignment="Center"/>
55             </Grid>
56             <Grid Height="30">
57                 <TextBlock Text="Contact - 4"
58                            VerticalAlignment="Center"/>
59             </Grid>
60             <Grid Height="30">
61                 <TextBlock Text="Contact - 5"
62                            VerticalAlignment="Center"/>
63             </Grid>
64             <Grid Height="30">
65                 <TextBlock Text="Contact - 6"
66                            VerticalAlignment="Center"/>
67             </Grid>
68             <Grid Height="30">
69                 <TextBlock Text="Contact - 7"
70                            VerticalAlignment="Center"/>
71             </Grid>
72             <Grid Height="30">
73                 <TextBlock Text="Contact - 8"
74                            VerticalAlignment="Center"/>
75             </Grid>
76         </ListBox>
77     </Grid>
78 </Page>
79 	
[Update, 2/11/08]

There are couple advantages of this technique.

  • You don’t have to recalculate the row color when you add or delete rows. This is a major problem with the ItemTemplateSelector technique and can cause a performance bottleneck.
  • You can easily swap a different DrawingBrush and make it customizable (skinnable).

One caveatof this technique is that the row-height of the ListBox should be fixed. It may be possible to bind the heights of the Rectangles in the DrawingBrush to the height of the ListBoxItem (thanks David for the idea), but I haven’t looked into it.

Hope you find it useful.