Feb 25, 2006

How to implement a data bound ListView

17BoundListView

Update: In WPF 3.5 SP1, WPF introduced built-in support for alternating rows, so my alternating row solution below is no longer necessary. See Vincent’s blog post for more information.


The ListView control allows us to display data in a tabular form. In this post, I will show you how easy it is to data bind a ListView to XML data. I will also show you how to style the ListViewItems such that they alternate background colors, even when data items are added and removed. Someone left a comment in one of my previous posts asking for a sample with GridView and GridViewRowPresenter. I will include those in this sample and explain their behavior.

I used an XML data source of the solar system planets in this sample. This data is added to the Window’s Resources in the following form:

    <Window.Resources>
        <XmlDataProvider XPath="/SolarSystemPlanets/Planet" x:Key="planets">
            <x:XData>
            <SolarSystemPlanets xmlns="">
                <Planet Name="Mercury">
                    <Orbit>57,910,000 km (0.38 AU)</Orbit>
                    <Diameter>4,880 km</Diameter>
                    <Mass>3.30e23 kg</Mass>
                    <Image>merglobe.gif</Image>
                    <Details>The small and rocky planet Mercury is the closest planet to the Sun.</Details>
                </Planet>
                (…)
            </SolarSystemPlanets>
            </x:XData>
        </XmlDataProvider>
        (…)
    </Window.Resources>

In particular, notice the addition of the x:XData element, new in Feb CTP, and the empty XML namespace (xmlns=”") on the SolarSystemPlanets element.

ListView has a View property of type ViewBase. Currently, the only class that derives from ViewBase is GridView, which can be used as in the following markup to display each of the data fields:

    <Window.Resources>
        (…)
        <DataTemplate x:Key="ImageTemplate">
            <Image Source="{Binding XPath=Image, Converter={StaticResource stringToImageSource}}" (…) />
        </DataTemplate>

        <DataTemplate x:Key="NameTemplate">
            <TextBlock Text="{Binding XPath=@Name}" FontWeight="Bold" (…) />
        </DataTemplate>
        (…)
    </Window.Resources>

    <ListView ItemsSource="{Binding Source={StaticResource planets}}" (…)>
        <ListView.View>
            <GridView>
                <GridView.Columns>
                    <GridViewColumn Header="Name" CellTemplate="{StaticResource NameTemplate}" />
                    <GridViewColumn Header="Orbit" DisplayMemberBinding="{Binding XPath=Orbit}" />
                    <GridViewColumn Header="Diameter" DisplayMemberBinding="{Binding XPath=Diameter}" />
                    <GridViewColumn Header="Mass" DisplayMemberBinding="{Binding XPath=Mass}" />
                    <GridViewColumn Header="Image" CellTemplate="{StaticResource ImageTemplate}" />
                </GridView.Columns>
            </GridView>
        </ListView.View>
    </ListView>

You can use one of two GridViewColumn properties to control the way each planet data item is displayed: CellTemplate or DisplayMemberBinding. CellTemplate takes a DataTemplate, allowing maximum flexibility in how we visualize the data. In this particular sample, I used it for the “Name” column because I wanted it to be bold and for the “Image” column because I wanted to display the actual image (and not the name of the image, which is what is stored in the data source). DisplayMemberBinding (notice this was renamed from DisplayMemberPath in Jan CTP) should be used when we want to display the data we are binding to directly in a TextBlock.

This is all it takes to display data in a ListView – it’s really simple. This is great, but we want to make the data more readable by alternating the background color from one row to the next. We need the help of a StyleSelector to accomplish this. I talked in my Jan 14 post about using a DataTemplateSelector to display some data items differently from others, based on custom logic. A StyleSelector does the same thing, but instead allows us to pick a different Style for each ListViewItem, which is what we want in this scenario. In case you are not familiar with the very important difference between styles and templates: styles allow you to set properties on any FrameworkElement; templates completely override the look of a particular Control (ControlTemplate) or its data portion (DataTemplate).

    <Window.Resources>
        (…)

        <Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemStyleBase}" x:Key="ListViewItemStyle1">
            <Setter Property="Background" Value="White" />
        </Style>

        <Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemStyleBase}" x:Key="ListViewItemStyle2">
            <Setter Property="Background" Value="AliceBlue" />
        </Style>

        <local:ListViewItemStyleSelector x:Key="ListViewItemStyleSelector" />
    </Window.Resources>

    <ListView ItemContainerStyleSelector="{StaticResource ListViewItemStyleSelector}" (…)>

    public class ListViewItemStyleSelector : StyleSelector
    {
        private int i = 0;
        public override Style SelectStyle(object item, DependencyObject container)
        {
            // makes sure the first item always gets the first style, even when restyling
            ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
            if (item == ic.Items[0])
            {
                i = 0;
            }
            string styleKey;
            if (i % 2 == 0)
            {
                styleKey = "ListViewItemStyle1";
            }
            else
            {
                styleKey = "ListViewItemStyle2";
            }
            i++;
            return (Style)(ic.FindResource(styleKey));
        }
    }

I added to the Window’s Resources the two Styles I want to alternate. I want ListViewItems with an even index to be white, and the odd ones to be AliceBlue. As you can see above, the StyleSelector contains simple logic to return the correct Style for each item.

Notice that these Styles have a BasedOn property set to some other Style. The BasedOn property behaves similarly to object inheritance. I will show you the base Style shortly.

This works great as long as you never add a new item to the source. If a new item is added, this code will calculate the Style for the newly added item but it will not redo the work for the other items. You will end up with two consecutive items of the same color, which is not what you want.

There are probably many ways to cause the StyleSelector to reassign the Styles for all items. In this sample, I attached a handler to the CollectionChanged event of the data source, and there I set the ListView’s ItemContainerStyleSelector property to null and back to the same StyleSelector. If I don’t set it to null first, Avalon is smart enough to not redo the Style assignment.

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        ((INotifyCollectionChanged)lv.Items).CollectionChanged += new NotifyCollectionChangedEventHandler(Restyle);
    }

    private void Restyle(object sender, NotifyCollectionChangedEventArgs args)
    {
        StyleSelector selector = lv.ItemContainerStyleSelector;
        lv.ItemContainerStyleSelector = null;
        lv.ItemContainerStyleSelector = selector;
    }

Finally, I want the selected items to behave similarly to the ones in Add/Remove Programs. When I click on a planet, I want its details to appear in a single line, below the rest of the data, and spanning across all columns.

The default template for ListViewItem contains a Border wrapping a GridViewRowPresenter. The GridViewRowPresenter is the one that does all the actual work of displaying each individual data item in tabular form. You can think of the GridView simply as a common container for the GridViewColumns, styles and templates and a few other things.

When GridViewRowPresenter is used in the template for a ListViewItem and its Columns property is not set, it uses the collection of GridViewColumns set in the GridView automatically. All the information it needs to display the data is in the collection of GridViewColumns. The use of GridViewRowPresenter is not exclusive to ListView/GridView, though. If you set its Columns property directly, you can use it to template anything you can think of. I could go into more detail on this, but instead I will highly recommend you keep an eye on our ATC’s team blog; they have some really cool stuff coming up.

(By the way, you can find the default templates for all our controls by using Sparkle — I mean, Expression Interactive Designer.)

Once you see the default template for ListViewItem and understand the role of GridViewRowPresenter, it is easy to get the details line to span across the columns: you simply need to add a TextBlock below the GridViewRowPresenter, as you can see in the markup below. (This is the base Style for the alternating Styles I mentioned before.)

    <Style TargetType="{x:Type ListViewItem}" x:Key="ListViewItemStyleBase">
        (…)
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListViewItem}">
                    <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                        <StackPanel>
                            <GridViewRowPresenter />
                            <TextBlock Text="{Binding XPath=Details}" Visibility="Collapsed" Name="tb" TextWrapping="Wrap" Margin="2,0,0,4"/>
                        </StackPanel>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Trigger.Setters>
                                <Setter Property="Visibility" Value="Visible" TargetName="tb"/>
                            </Trigger.Setters>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        (…)
    </Style>

I don’t want the details to be visible all the time, though. I only want them to show for the selected item. To accomplish this, I first set the visibility of the TextBlock to collapsed (meaning that it takes no space in the layout, in addition to being invisible). I then added a Trigger that changes the TextBlock to be visible only when the ListViewItem is selected.

Here is a screenshot of the completed sample:

Here you can find the VS project with this sample code. This works with February CTP WPF bits.

Update September 17, 2007: Here you can find this project running in Orcas Beta 2 bits.

39 Comments
  1. Alex

    Thank you very much, Beatriz!

    Your examples are great as usual.

    I have one question, though.

    Whatever I do, I cannot enable vertical scrollbar in the ListView/GridView in your example.

    How to do it properly?

    Regards,
    Alex

    • Bea

      Hi Alex,

      I’m glad you liked the sample.

      Do you want to keep the headers in place and only scroll the content of the ListView? I’m guessing this is what you want. If so, you can simply set the Height of the ListView to something smaller than what it needs. If you want to scroll the whole thing, including headers, add a ScrollViewer around the ListView with a Height set on it.

      • Alex

        Hi, Beatriz

        Regarding the scrollbar. What I wanted is that vertical scrollbar appears and disappears when needed. When I resize the window – horizontal bar works as expected, however, vertical bar never shows up and setting of ScrollViewer.VerticalScrollBarVisibility=”Auto” on the ListView didn’t help.

        Thank You,
        Alex

        • Bea

          Hi Alex,

          Ah, I hand’t understood you wanted the scrollbar to appear when resizing the window. In that case, replace the StackPanel in the app with a Grid or DockPanel. StackPanel (when stacking vertically) allows as much space as controls need vertically, and as a result it will never cause scrollbars to appear. Grid limits the space given to its content to what’s visible (both vertically and horizontally), so the ListView will get scrollbars.

          Let me know if this works for you.

        • Alex

          Well, Beatriz, there are good news and there are bad news.

          1. Replacing StackPanel with DockPanel helped to find missed vertical scrollbar – thank you.

          2. The alternate row coloring doesn’t work anymore. If ListView is resized so that only three or four rows are visible then after addition of a new row you can get several adjacent rows of the same color.

          I guess, Boris, this is the answer to your question – StyleSelector is not called for invisible rows or at least not for all of them.

          Alex

        • Bea

          Alex (and Boris),

          Yes, it would be a waste to call StyleSelector for all items that are not visible, and we don’t do that with the default implementation of ListView, as Alex said. ListView uses VirtualizingStackPanel by default to lay out its items, meaning it only generates containers (ListViewItems) for the data that is visible. Notice also that when you scroll, items that become not visible are not de-virtualized right away. We keep them around for a while until we reach a certain number of them, and then start throwing them away. This is also the default panel for ListBox.

          Right, it makes sense that due to our virtualization of items, scrolling does not work well with the this solution for alternating row color. If you want scrolling, the best (simplest, most efficient) solution for alternating items I can think of is the one Boris mentioned in his first comment. You can get the index of each item by doing the following:

          int i = ic.ItemContainerGenerator.IndexFromContainer(container);

          If you have very few items, this solution or changing the panel of ListView to be the non-virtualized StackPanel (through the ItemsPanel property) don’t differ much in terms of performance. If you have lots of items this solution is much better.

          If you guys can think of a better way to do this, I would be interested to hear it. Isn’t this fun? :)

          Bea

        • Alex

          Beatriz,

          I always enjoy reading your posts.

          The last one immediately fixed the problem with the alternate styling.

          You are the best.
          Alex

  2. Boris

    Hi, Beatriz

    Is it a hack, or StyleSelector is always called sequentially for every row in the GridView? Is there any way to get internal row number or index from ListViewItem?

    • Bea

      StyleSelector is called once for every item when they are initially generated. If a new item is added, StyleSelector is called just for the newly added item, it is NOT called for all the others that are already generated, by default. Generally this is what you want, but in this particular case, if you add an item, you want to redefine the background colors of all the items that come after the newly added one. I did this by causing the ItemContainerStyleSelector property to be invalidated, which causes all items to be restyled.

      Yes, you can get the index when you have a ListViewItem by using the ItemContainerGenerator. For more information look at my blog post on How can I get a ListBoxItem from a data bound ListBox?

      If I understand correctly, you are thinking of checking the index of the ListViewItem in every call of SelectStyle and only change the ones that have index after the items you added, is this correct? Notice that although you can get the index of a ListViewItem, that is a O(n) operation. If you do that for every SelectStyle call, you will end up with an O(n^2) solution. My current solution changes the style of the items with lower indices unnecessarily, but it is an O(n) operation.

      There’s most certainly other ways to achieve the same result though, and they may have some advantages over the one in this sample. This is a good area for us to think more throughly for Avalon V2. If we see that this is a very common scenario, we may provide better support for it out of the box.

      • Boris

        Hi, Beatriz

        Thank you very much for your detailed answer about StyleSelector.

        I also appologize for not signing my post (my name is Boris), I understand that it’s hard to keep the conversation with more than one anonymous speaker.

        I would like to clarify one more point here (which is somewhat related to the Alex’s question about sclollbar).

        Let say I have thousand of records, of which only ten are visible in the scroll area of the ListView. Does your answer mean that StyleSelector will be called for thousand of rows even though 990 of them may even never be actually displayed? Wouldn’t it be a waste of memory and time to do so?

        Best Regards,
        Boris

        • Bea

          (Alex and) Boris,

          Yes, it would be a waste to call StyleSelector for all items that are not visible, and we don’t do that with the default implementation of ListView, as Alex said. ListView uses VirtualizingStackPanel by default to lay out its items, meaning it only generates containers (ListViewItems) for the data that is visible. Notice also that when you scroll, items that become not visible are not de-virtualized right away. We keep them around for a while until we reach a certain number of them, and then start throwing them away. This is also the default panel for ListBox.

          Right, it makes sense that due to our virtualization of items, scrolling does not work well with the this solution for alternating row color. If you want scrolling, the best (simplest, most efficient) solution for alternating items I can think of is the one Boris mentioned in his first comment. You can get the index of each item by doing the following:

          int i = ic.ItemContainerGenerator.IndexFromContainer(container);

          If you have very few items, this solution or changing the panel of ListView to be the non-virtualized StackPanel (through the ItemsPanel property) don’t differ much in terms of performance. If you have lots of items this solution is much better.

          If you guys can think of a better way to do this, I would be interested to hear it. Isn’t this fun? :)

          Bea

  3. Anonymous

    Hello,

    I cannot find Expressions Interactive Designer that works with the February CTP. Is there one out somwhere?

    • Bea

      No, there is not Expression Interactive Designer publicly available for our Feb CTP yet. It will come out very soon though, so stay tuned.

  4. Sam

    ListView is great – but somehow I am too stupid to bind it to a datacontent instead of an embedded resource :(

    Can you point me to a sample how to bind a ListView to a DataSet, or a generic List of some custom struct?

    Please!

    Pretty please with sugar on top? :)

    • Bea

      Hi Sam,

      I made a sample with a ListView data bound to a DataTable for you. You can find it here.

      Let me know if this is what you were looking for.

      Bea

      • Sam

        Aww, nice, thanks a lot!

        I had not been able to compile it using the feb ctp, but gleaned enough from it to learn how to do it myself.

        Thanks a lot!
        Sam (off to put a textbox in the grid now)

  5. Chris (MueMeister)

    Hi Beatriz,

    Thanks for the example.
    My problem is about ListView, too but I’m not sure whether it fits to this topic.
    I want to drag and drop ListViewItem. I want to provide custom sorting functionality
    where a user can drag and drop list items within the same listview object.
    I want to visualize this in a way that the ListView moves its children to make place for the dragged item. Since a panel takes over the layout process of a ListView this have to be the task of that panel. Last but
    not least I bind the ListView in a DataBinding. This permits to remove
    child elements on Panel level – so i have to do this on ListView level!

    Did you ever try to drag an element within a listview/listbox? It is horrible! If a user
    selects a listviewitem and I want him to drag it, how do you check whether
    the mouse is over the item or not since ListView.Items are data collections and no FrameworkElements (ListViewItem). Or even more easy: How do you get the target drop location? I mean which index is the one to include the item in the ListView data again? Please help me on this significant feature. Do You have some hints to realize such a szenario?

    Thank You for Your help.
    Chris

    • Bea

      Chris,

      I haven’t replied to your question before because I was hoping to find some time to implement that scenario myself and understand the issues you are talking about. Unfortunately this hasn’t happened yet.

      I will implement it as soon as I have some time, but I don’t want you to wait for me for an answer. You may want to post your question in the Avalon Forums. I have heard of people within Microsoft that have been able to successfuly implement that scenario, and hopefully they can help you better and faster. I also heard that they struggled quite a bit in the process, so they will understand the issues you’re experiencing.

      I will get back to you when I explore this scenario.

  6. Anonymous

    Hi Beatriz,

    Thank you for such a nice blog. I learn a lot from you. I’ve downloaded Visual C# Express and the March CTP of Expression Interactive Designer.

    Maybe you can help me to find a resource about a situation i have to deal with.

    Its more of a data issue then UI issue. I need to find how to work with only a subset of my data. Kind of a view of a bigger list. The thing is, i’m going to have around 100,000 items and of course, i dont want all these items to be loaded in RAM (dataset). But at the same time, i want to be able to show that there could be 100,000 items (scrollbar ?). In a way this could resemble Paging.

    Where could i get good information regarding this kind of optimization to data availability ?

    • Bea

      Hi,

      You are asking for what we call “Data Virtualization”. We had mega plans for implementing this for V1, but we didn’t get to it. We will for sure revisit this for V2 – it’s actually one of our top scenarios for V2.

      “Data Virtualization” is a hard problem to solve. I have talked to users who attempted to do it and said they weren’t able to have it working reliably. I’d be interested to hear your experience solving this problem.

  7. AhmedWebDev

    can i make this GridView in Pagable Form

    • Bea

      Hi,

      We don’t have any out-of-the-box support for making ListView pageable. It shouldn’t be too hard for you to do that yourself, though. I can think of a couple of solutions:

      - If you don’t need data virtualization, you could hide the ScrollViewer, and then add up and down buttons that hook up with the PageUp and PageDown commands of ScrollViewer.

      - If you need data virtualization, you could design your data layer so that it is able to return only the few data items you need to display at one point on the screen. If you can’t (or don’t want to) change your data layer, you can add a second layer that wraps your data and returns only the items you want.

      Thanks,
      Bea

  8. Vijay

    Hi Beatriz,

    Thanks for the wonderful sample. Can you please also explain how can we populate a listview programmatically using an XMLDataProvider(in cs file)

    • Bea

      Hi Vijay,

      Based on your question, I assume that you have your XML in a separate file (and not in XAML, like in this post). If that’s the case, here is the code that would allow you to populate the ListView programatically:

      xdp = new XmlDataProvider();
      xdp.Source = new Uri(“SolarSystemPlanets.xml”, UriKind.Relative);
      xdp.XPath = “/SolarSystemPlanets/Planet”;
      Binding b = new Binding();
      b.Source = xdp;
      lv.SetBinding(ListView.ItemsSourceProperty, b);

      You can find here a complete solution with this code.

      Thanks,
      Bea

  9. Bill

    I hope you see this on such an old entry, but you don’t have any way to contact you via e-mail here.

    I need to present a very similar ListView, with summary text on each row. However, for me it’s important that the text wrap, since for many entries it will be quite lengthy. Running your project on Orcas Beta 1 shows me that despite having TextWrapping=”Wrap” on the TextBlock, the summary text never wraps.

    I have a blog post on this topic here: http://wekempf.spaces.live.com/blog/cns!D18C3EC06EA971CF!246.entry. I’d appreciate any feedback you can provide.

    • Bea

      Hi Bill,

      Try setting ScrollViewer.HorizontalScrollBarVisibility=”Disabled” on your ListView. This worked for me, in the sample for this blog post.

      ScrollViewer.HorizontalScrollBarVisibility is set to Auto by default, which means that a horizontal scrollbar comes up when the content of the rows can not all be displayed. Setting it to Disabled will force all the content to fit in the ListView area. If your text is too long, it will wrap (provided that you enabled text wrapping).

      I uploaded a project to my server that shows this working – you can find it here.

      Thanks,
      Bea

      • Bill

        The only problem with this solution is that now the view can be smaller than the width of the columns, and there’s no way to scroll to see the data within the columns. :(

        I’ve tried a solution where you bind the MaxWidth to the ScrollViewer’s ViewportWidth, which appears to work splendidly at first. The problem is, if you resize the window decreasing the width, the text moves a corresponding size to the right as if you’d set a left margin. As of yet, I’ve not found an ideal solution for this scenario. :(

        • Bea

          Hi Bill,

          Yeah, I see the same behavior you’re describing. I was able to get around it by setting the HorizontalAlignment property of the TextBlock to Left. Try this:

          <TextBlock Text=”{Binding XPath=Details}” HorizontalAlignment=”Left” MaxWidth=”{Binding RelativeSource={RelativeSource AncestorType={x:Type ScrollViewer}}, Path=ViewportWidth}” Visibility=”Collapsed” Name=”tb” TextWrapping=”Wrap” Margin=”2,0,0,4″/>

          Or you can download my sample here.

          Let me know if this worked for you.

          Bea

        • Bill

          Works beautifully. I’ve actually got a more complex scenario, with numerous controls below the GridViewRowPresenter, and I’ve encased them all in a StackPanel with the HorizontalAlignment=”Left”, which definately fixes this odd behavior. Now what I’d love to understand is why we get the odd behavior and why this fixes it. Knowing a few hacks like this is helpful, but understanding why they work allows you to apply similar hacks in other situations. Any idea what’s going on here?

          In any event, thanks a million for helping me work around this specific scenario!

        • Bea

          Hi Bill,

          What was happening is that the text was wrapping, and therefore occupying a smaller width, but the total width of the ListView was not changing. My guess is that HorizontalAlignment was Center, causing the whole text to shift as we make its width smaller (as a consequence of resizing). By setting HorizontalAlignment to Left, the text will have no need to shift as it’s resized anymore.

          Does that make sense?

          I’m glad it worked for you!

          Bea

  10. Srinivas

    Hi All,

    How to avoid Drag Column headers in ListView control

    Please help me

    Thanbks & Regards
    Srinivas

    • Bea

      Hi Srinivas,

      I’m not sure which one you mean: do you want to avoid reordering the columns or resizing them? I’ll reply to both:

      - To avoid reordering them, you can set AllowsColumnReorder=”False” on the GridView.

      - To avoid resizing them, it’s a little harder. I was able to prevent resizing by copying the chrome template for the GridView header, and setting IsEnabled=”false” on the thumb gripper:

      <Canvas>
      <Thumb IsEnabled=”False” x:Name=”PART_HeaderGripper” Style=”{StaticResource GridViewColumnHeaderGripper}”/>
      </Canvas>

      To get the chrome template for the GridView header, go to Blend, drag a GridViewColumnHeader, right click and pick “Edit Control Parts (Template)”, and then “Edit a copy”.

      I made a sample that prevents reordering and resizing – you can find it here.

      I hope this helps.

      Bea

  11. Haridas

    I’d like to know if there is a way to Freeze certain GrideViewcolumns
    in a list View in WPf(As that of Column Freezing in an Excel Application).

    • Bea

      Hi Haridas,

      We don’t support that feature in the current implementation of ListView. I also can’t think of a reasonable workaround that will enable that. I agree with you that it would be handy, though. I’ll communicate that to the rest of the team.

      Thanks,
      Bea

  12. abcd

    Thx!

  13. Mark

    Hi Bea,

    All your blogs are great. Just one thing you can use the indexOf property of the items collection to simplify your SelectStyle function:

    public override Style SelectStyle(object item, DependencyObject container)
    {
    var ic = ItemsControl.ItemsControlFromItemContainer(container);

    return (Style)(ic.FindResource(
    ic.Items.IndexOf(item) % 2 == 0 ? “ListViewItemStyle1″ : “ListViewItemStyle2″));
    }

    • Bea

      Hi Mark,

      That would simplify the code, but it would make it less performant. IndexOf does a linear search – O(n) – so doing that for each item would result in a O(n2) solution. My solution is a bit more verbose, but it executes in O(n).

      Also, WPF now has support for alternating rows. To get this working, you can set ItemsControl’s AlternationCount property to the number of different styles you want to have. Then you can use a trigger to set different properties for each row. For more information, look at Vincent’s blog post.

      Bea

  14. Alan

    I took your ListViewItemStyleSelector class and added a bit of functionality to it that I thought I would share. I setup the class to take an arbitrary number of styles to alternate between (a minimum of 2).

    public class ListViewItemAlternatingStyleSelector : StyleSelector
    {
    private int i = 0;
    private List<string> styleKeys;

    public ListViewItemAlternatingStyleSelector(string styleKey1, string styleKey2, params string[] styleKeys)
    {
    this.styleKeys = new List<string>(styleKeys.Length + 2);
    this.styleKeys.Add(styleKey1);
    this.styleKeys.Add(styleKey2);
    this.styleKeys.AddRange(styleKeys);
    }

    public override Style SelectStyle(object item, DependencyObject container)
    {
    ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);

    if (item == ic.Items[0])
    {
    i = 0;
    }

    return (Style)ic.FindResource(styleKeys[i++ % this.styleKeys.Count]);
    }
    }

    To construct the class in XAML, you simply use an ObjectDataProvider.

    <ObjectDataProvider x:Key=”ListViewItemAlternatingStyleSelector” ObjectType=”{x:Type ctrls:ListViewItemAlternatingStyleSelector}”>
    <ObjectDataProvider.ConstructorParameters>
    <sys:String>ListViewItemStyle1</sys:String>
    <sys:String>ListViewItemStyle2</sys:String>
    <sys:String>ListViewItemStyle3</sys:String>
    <sys:String>ListViewItemStyle4</sys:String>
    </ObjectDataProvider.ConstructorParameters>
    </ObjectDataProvider>
    <Style TargetType=”{x:Type ListViewItem}” x:Key=”ListViewItemStyle1″>
    <Setter Property=”Background” Value=”Transparent” />
    </Style>
    <Style TargetType=”{x:Type ListViewItem}” x:Key=”ListViewItemStyle2″>
    <Setter Property=”Background” Value=”Honeydew” />
    </Style>
    <Style TargetType=”{x:Type ListViewItem}” x:Key=”ListViewItemStyle3″>
    <Setter Property=”Background” Value=”Transparent” />
    </Style>
    <Style TargetType=”{x:Type ListViewItem}” x:Key=”ListViewItemStyle4″>
    <Setter Property=”Background” Value=”AliceBlue” />
    </Style>

    And of course we must update the binding to support the ObjectDataProvider.

    <ListView ItemContainerStyleSelector=”{Binding Source={StaticResource ListViewItemAlternatingStyleSelector}}”>

    • Bea

      Hi Alan,

      Thanks for sharing your code.

      Also, keep in mind that WPF 3.5 has a built-in feature that allows alternating rows in a more efficient way than my solution here. You can read more about it in Vincent’s blog.

      Thanks,
      Bea

Comments are closed.