Alternating Colors of rows in ListView in Windows

2019-02-06 10:16发布

问题:

I have created a Windows Phone 8.1 run-time app.

I am using the ListView control.

I want to alternate the each background row color.

After searching I found this link a previous answer.

But this gives errors in the markup. For one thing there is no 'AlternationCount' property. I am assuming this is because it is not SilverLight but RT?

If anyone can send me a link as I am struggerling to find a simple example. even better a simple code example would be appreciated.

回答1:

My proposal is to use a Converter class with additional DependencyProperties. When you intialize the converter, you define to which items collection it will refer and a list of alternate brushes for a background. It can look for example like this:

public class AlternateConverter : DependencyObject, IValueConverter
{
    public List<SolidColorBrush> AlternateBrushes
    {
        get { return (List<SolidColorBrush>)GetValue(AlternateBrushesProperty); }
        set { SetValue(AlternateBrushesProperty, value); }
    }

    public static readonly DependencyProperty AlternateBrushesProperty =
        DependencyProperty.Register("AlternateBrushes", typeof(List<SolidColorBrush>), 
        typeof(AlternateConverter), new PropertyMetadata(new List<SolidColorBrush>()));

    public object CurrentList
    {
        get { return GetValue(CurrentListProperty); }
        set { SetValue(CurrentListProperty, value); }
    }

    public static readonly DependencyProperty CurrentListProperty =
        DependencyProperty.Register("CurrentList", typeof(object),
        typeof(AlternateConverter), new PropertyMetadata(null));

    public object Convert(object value, Type targetType, object parameter, string language)
    { return AlternateBrushes[(CurrentList as IList).IndexOf(value) % AlternateBrushes.Count]; }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    { throw new NotImplementedException(); }
}

Once you have it defined and create list of alternate brushes:

// somewhere in your DataContext
private List<SolidColorBrush> brushes = new List<SolidColorBrush> { new SolidColorBrush(Colors.Red), new SolidColorBrush(Colors.Blue) };
public List<SolidColorBrush> Brushes { get { return brushes; } }

You can use it like this:

<ListView x:Name="myList" ItemsSource={Binding MyItems}>
  <ListView.Resources>
    <local:AlternateConverter CurrentList="{Binding ElementName=myList, Path=ItemsSource}" 
                                      AlternateBrushes="{Binding Brushes}"
                                      x:Key="AlternateConverter"/>
  </ListView.Resources>
  <ListView.ItemTemplate>
     <DataTemplate>
       <Border Background="{Binding Converter={StaticResource AlternateConverter}}">
          <!-- your itemtemplate -->
       </Border>
     </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

This solution should work, though it may have problem when you have IList of value types. Also here shouldn't be problems with deferred creation, as it retrives the index directly from list.



回答2:

I know there's already a few good answers to this question but I just want to throw in one more idea which I think it's a bit harder to implement but easier to use.

This solution will need the help from the ListView's ItemContainerStyleSelector and a Behavior from Behavior SDK (XAML).

Basically, this AlternatingColorItemContainerStyleSelector behavior I've created allows you to specify two SolidColorBrush colors. It encapsulates the logic of creating a ItemContainerStyleSelector with two different Styles as well as assigning the corresponding SolidColorBrush to each Style.

Once you have the behavior in place, to use it is extremely simple - I only needed to drag and drop it onto the ListView in Expression Blend and specify two colors and that's it!

Here's the behavior.

namespace Behaviors
{
    public class AlternatingColorItemContainerStyleSelector : StyleSelector
    {
        private Style _oddStyle = new Style { TargetType = typeof(ListViewItem) }, _evenStyle = new Style { TargetType = typeof(ListViewItem) };
        public Style OddStyle { get { return _oddStyle; } }
        public Style EvenStyle { get { return _evenStyle; } }

        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            var listViewItem = (ListViewItem)container;
            var listView = GetParent<ListView>(listViewItem);

            var index = listView.IndexFromContainer(listViewItem);

            if (index % 2 == 0)
            {
                return this.EvenStyle;
            }
            else
            {
                return this.OddStyle;
            }
        }

        public static T GetParent<T>(DependencyObject child) where T : DependencyObject
        {
            while (!(child is T))
            {
                child = VisualTreeHelper.GetParent(child);
            }

            return (T)child;
        }
    }

    public class ListViewAlternatingColorBehavior : DependencyObject, IBehavior
    {
        public DependencyObject AssociatedObject { get; set; }

        public Style SharedItemContainerStyle { get; set; }

        #region colors dp

        public SolidColorBrush OddBrush
        {
            get { return (SolidColorBrush)GetValue(OddBrushProperty); }
            set { SetValue(OddBrushProperty, value); }
        }

        public static readonly DependencyProperty OddBrushProperty =
            DependencyProperty.Register("OddBrush", typeof(SolidColorBrush), typeof(ListViewAlternatingColorBehavior), new PropertyMetadata(null));

        public SolidColorBrush EvenBrush
        {
            get { return (SolidColorBrush)GetValue(EvenBrushProperty); }
            set { SetValue(EvenBrushProperty, value); }
        }

        public static readonly DependencyProperty EvenBrushProperty =
            DependencyProperty.Register("EvenBrush", typeof(SolidColorBrush), typeof(ListViewAlternatingColorBehavior), new PropertyMetadata(null));

        #endregion

        public void Attach(DependencyObject associatedObject)
        {
            this.AssociatedObject = associatedObject;

            this.ApplyItemContainerStyleSelectors();
        }

        private void ApplyItemContainerStyleSelectors()
        {
            var itemContainerStyleSelector = new AlternatingColorItemContainerStyleSelector();

            if (this.SharedItemContainerStyle != null)
            {
                itemContainerStyleSelector.OddStyle.BasedOn = this.SharedItemContainerStyle;
                itemContainerStyleSelector.EvenStyle.BasedOn = this.SharedItemContainerStyle;
            }

            itemContainerStyleSelector.OddStyle.Setters.Add(new Setter { Property = Control.BackgroundProperty, Value = this.OddBrush });
            itemContainerStyleSelector.EvenStyle.Setters.Add(new Setter { Property = Control.BackgroundProperty, Value = this.EvenBrush });

            var listView = (ListView)this.AssociatedObject;
            listView.ItemContainerStyleSelector = itemContainerStyleSelector;
        }

        public void Detach()
        {
        }
    }
}

One thing to note is that removing items won't update all the other items' colors (simply because SelectStyleCore of other items won't be called), adding items will. But in your case this should be enough.



回答3:

WPF is the only framework that supports "AlternationCount" -- neither Windows Phone nor Silverlight nor RT have it.

You might find that the easiest solution is just to add an "Index" or "IsOdd" property to your row model. The you can just bind to that property, using a converter to return the appropriate brush/color depending on the index.

One simpler approach is to bind to a converter that keeps track of the index using a static variable, like below. However, if you use item virtualization (which you probably do unless you have only a few items), then this approach runs into glitches: the deferred-creation of the UI elements results in indices getting assigned out-of-order, and you end up with consecutive rows showing same color.

<Border Background="{Binding Converter={StaticResource AlternatingIndexConverter}}">

public class AlternatingIndexConverter : IValueConverter
{
    private static int _index;

    public Brush Even { get; set; }
    public Brush Odd { get; set; }

    public object Convert(...)
    {
        return (_index++ % 2 == 0 ? Even : Odd);
    }
}


回答4:

For me the sleekest way to do it is:

    private void ListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (args.ItemIndex%2 != 0)
        {
            args.ItemContainer.Background = new SolidColorBrush(Colors.Aqua);
        }
        else
        {
            args.ItemContainer.Background = new SolidColorBrush(Colors.White);
        }
    }

Just hook you up to the ContainerContentChanging-Event of your ListView. I don't know if it works when your resort your list but for normal case it works very well.

You can even implement your own ListView, so you can use it as much as you like. With the right propertys you can also edit it in the xaml file. For example asigning #FFFF0000 (ARGB).

public class BackgroundAlternatingListView : ListView
{
    private Color _startColor = Colors.White;
    private Color _secondColor = new Color { A = 255, R = 198, G = 198, B = 198 };

    public Color StartColor
    {
        get { return _startColor; }
        set { _startColor = value; }
    }

    public Color SecondColor
    {
        get { return _secondColor; }
        set { _secondColor = value; }
    }


    public BackgroundAlternatingListView()
    {
        ContainerContentChanging += BackgroundAlternatingListView_ContainerContentChanging;
    }

    void BackgroundAlternatingListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        if (args.ItemIndex % 2 != 0)
        {
            args.ItemContainer.Background = new SolidColorBrush(_secondColor);
        }
        else
        {
            args.ItemContainer.Background = new SolidColorBrush(_startColor);
        }
    }
}


回答5:

Thanks for both your replies - much appreciated. I have another solution I would like to propose and I post it here for people to comment on.

lvwPremises.Items.Clear();
bool toggle = false;
foreach (Premise premise in Shared.Premises)
{
     ListViewItem item = new ListViewItem();
     item.Content = premise.PremiseName;
     if (toggle)
     {
         item.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 223, 240, 216));
     }
     else
     {
         item.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 208, 233, 198));
     }
     lvwPremises.Items.Add(item);
     toggle = !toggle;
 }

EDIT - by Romasz

You can always play in the code if you want, but then your solution is not so universal, will have to be run always when you change collection and may have other problems. I placed this comment as an edit to your answer, hence the code above may be simplified and it can look like this (looks more nice in the answer):

private void ColorBackgrounds(ListView list, IList<SolidColorBrush> backgrounds)
{
    for (int i = 0; i < list.Items.Count; i++)
        (list.ContainerFromIndex(i) as ListViewItem).Background = backgrounds[i % backgrounds.Count];
}