Mobile Zone is brought to you in partnership with:

Matt has been paid to develop software for the past 12 years. He specializes in mobile and web development and has recently been doing a lot with Windows Phone 7. He runs DevEvening (http://devevening.co.uk/) a .net focused user group in Surrey and the Windows Phone User Group (http://wpug.net/) in London. He blogs at http://blog.mrlacey.co.uk/ and tweets at @mrlacey & @wpug. Matt is a DZone MVB and is not an employee of DZone and has posted 103 posts at DZone. You can read more from them at their website. View Full User Profile

How Not to Highlight Alternate Items in a Virtualized List

01.27.2014
| 3508 views |
  • submit to reddit
It's a common requirement: highlighting alternate items in a list. There are various reasons for wanting to do this and almost infinite ways to visually represent the highlighted item.

As I've been asked to show an example of how to do this right, I present this simple example.

Grab the full source.

Here's all the CS.

namespace AlternateRows
{
    using System;
    using System.Collections.ObjectModel;
    using System.Windows.Data;
    using System.Windows.Media;

    using Microsoft.Phone.Controls;

    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();

            this.MainVm = new MainPageViewModel();

            this.BadList.ItemsSource = this.MainVm;
            this.GoodList.ItemsSource = this.MainVm;
        }

        public MainPageViewModel MainVm { get; set; }
    }

    public class MainPageViewModel : ObservableCollection<Entry>
    {
        public MainPageViewModel()
        {
            this.Add(new Entry(1, "Andrew"));
            this.Add(new Entry(2, "Barbara"));
            this.Add(new Entry(3, "Charles"));
            this.Add(new Entry(4, "Daphne"));
            this.Add(new Entry(5, "Edward"));
            this.Add(new Entry(6, "Francesca"));
            this.Add(new Entry(7, "Gordon"));
            this.Add(new Entry(8, "Harriette"));
            this.Add(new Entry(9, "Izaak"));
            this.Add(new Entry(10, "Juliette"));
            this.Add(new Entry(11, "Keith"));
            this.Add(new Entry(12, "Laura"));
            this.Add(new Entry(13, "Matthew"));
            this.Add(new Entry(14, "Norah"));
            this.Add(new Entry(15, "Owen"));
            this.Add(new Entry(16, "Patricia"));
            this.Add(new Entry(17, "Quentin"));
            this.Add(new Entry(18, "Roberta"));
            this.Add(new Entry(19, "Stephen"));
            this.Add(new Entry(20, "Tammy"));
            this.Add(new Entry(21, "Uriah"));
            this.Add(new Entry(22, "Violet"));
            this.Add(new Entry(23, "William"));
            this.Add(new Entry(24, "Xanthe"));
            this.Add(new Entry(25, "Yusuf"));
            this.Add(new Entry(26, "Zelda"));
        }
    }

    public class Entry
    {
        public Entry(int id, string name)
        {
            this.Id = id;
            this.Name = name;
        }

        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class BadConverter : IValueConverter
    {
        bool flag;

        SolidColorBrush whiteBrush = new SolidColorBrush(Colors.White);
        SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            flag = !flag;
            return flag ? whiteBrush : redBrush;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class GoodConverter : IValueConverter
    {
        SolidColorBrush whiteBrush = new SolidColorBrush(Colors.White);
        SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return (int)value % 2 == 1 ? whiteBrush : redBrush;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

And here's the corresponding XAML.

<phone:PhoneApplicationPage
    x:Class="AlternateRows.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:alternateRows="clr-namespace:AlternateRows"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"
    shell:SystemTray.IsVisible="True">

    <phone:PhoneApplicationPage.Resources>
        <alternateRows:BadConverter x:Key="BadConverter" />
        <alternateRows:GoodConverter x:Key="GoodConverter" />
    </phone:PhoneApplicationPage.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <phone:Pivot Title="MY APPLICATION">
            <phone:PivotItem Header="bad">
                <Grid>
                    <ListBox x:Name="BadList">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}"
                                           FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}"
                                           Foreground="{Binding Converter={StaticResource BadConverter}}"
                                           Margin="12" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </phone:PivotItem>

            <phone:PivotItem Header="good">
                <Grid>
                    <phone:LongListSelector x:Name="GoodList">
                        <phone:LongListSelector.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}"
                                       FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}"
                                       Foreground="{Binding Id, Converter={StaticResource GoodConverter}}"
                                       Margin="12" />
                            </DataTemplate>
                        </phone:LongListSelector.ItemTemplate>
                    </phone:LongListSelector>
                </Grid>
            </phone:PivotItem>
        </phone:Pivot>
    </Grid>

</phone:PhoneApplicationPage>

This code is for a page containing a pivot with 2 items. The first contains a ListBox using the "BadConverter" and the second contains a LongListSelector using the "GoodConverter". The same view model is bound to both lists.

The simple way to do the highlighting is with a converter. In the bad example the converter uses it's own reference to keep track of which items to highlight. (It just alternates the color that it applies to the items as they are realized.) The good example always applies the color based on a property of the item. This will always be the same for each item.

This means that if the lists are scrolled quickly the bad list may end up highlighting items in an order different to the one they are displayed in but the good list will always be highlighted based on the identifying property of the items, assuming that the items are in the correct order.

Obviously there are some assumptions in the above that it's probably good to make clear:

  • The use of a LongListSelector is only to help remind that the ListBox is deprecated in favor of the LongListSelector.
  • The "Id" property is used exclusively for determining the highlighting color. It has no other value. If the underlying property has an "Id" property that represents something else and might not always be in a sequential order, with no gaps, then creating another property ("PositionInList"?) for this purpose would be appropriate.
  • The values of the "Id" property could just be "1" and "2" repeatedly. For this example they don't need to increment.
  • The "Entry" class could have a property that returns a color and this could be bound to the display. This probably couple the display logic to the underlying view model too strongly. MVVM purists will have very strong objections to this ;)

I hope this helps.


Published at DZone with permission of Matt Lacey, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)