Статьи

Как не выделять альтернативные элементы в виртуализированном списке


Это общее требование: выделение альтернативных элементов в списке.
Существуют различные причины для желания сделать это и почти бесконечные способы визуально представить выделенный элемент.

Когда меня попросили показать пример того, как это сделать правильно, я представляю этот простой пример.

Возьмите полный источник .

Вот и все 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();
        }
    }
}

И вот соответствующий 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.