Alternating Colors of rows in ListView in Windows Phone 8.1
Asked Answered
E

5

11

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.

Esoterica answered 22/12, 2014 at 17:52 Comment(0)
A
5

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.

Antiperspirant answered 23/12, 2014 at 7:29 Comment(5)
Do you have an answer to my question related to your code at #28622414 ?Upbow
If you need to sort the list or swap items this solution won't work.Dozer
@ElMarchewko Yes, the code above will work only when list is created. If you need to do more advenced cases, then the method will need some improvements.Antiperspirant
@Antiperspirant but how? Is it possible to trigger an update within the item binding?Dozer
@ElMarchewko In above case the background is set only when item is created. I'm not sure if the easiest solution in your case wouldn't be just to add additional property in your item responsible for number in a list, then you can bind it with backgroundcolor and invoke every time you need it.Antiperspirant
F
6

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!

enter image description here

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.

Farmhouse answered 23/12, 2014 at 13:20 Comment(1)
Hi, I never knew I was going to get such good quality answers to this particular question. Thank You!Esoterica
A
5

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.

Antiperspirant answered 23/12, 2014 at 7:29 Comment(5)
Do you have an answer to my question related to your code at #28622414 ?Upbow
If you need to sort the list or swap items this solution won't work.Dozer
@ElMarchewko Yes, the code above will work only when list is created. If you need to do more advenced cases, then the method will need some improvements.Antiperspirant
@Antiperspirant but how? Is it possible to trigger an update within the item binding?Dozer
@ElMarchewko In above case the background is set only when item is created. I'm not sure if the easiest solution in your case wouldn't be just to add additional property in your item responsible for number in a list, then you can bind it with backgroundcolor and invoke every time you need it.Antiperspirant
S
2

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);
    }
}
Sculley answered 22/12, 2014 at 23:17 Comment(1)
Hi, thanks for posting this answer. It was close to what I wanted but the other answer gave me more. ThanksEsoterica
D
2

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);
        }
    }
}
Disconcerted answered 12/3, 2015 at 17:9 Comment(0)
E
0

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];
}
Esoterica answered 23/12, 2014 at 9:15 Comment(7)
Personally I'd avoid using ListViewItem directly like this.Farmhouse
Hi Justin, I am all for learning best practices and quite willing to change my ways (hence why I posted my own answer) but can you elaborate WHY you would not do it like this? ThanksEsoterica
I've edited your answer and added small comment in it - of course you can do it like this, everything can be changed via code. But what you would do when you change your list, add some items?Antiperspirant
Hi, thanks for all your comments. Really appreciated. You are quite right, modifying the collection means running that code all over again. I am afraid I was stuck in an nonobjective mode. My list is normally a small list of up to 10 items and is pulled from an Web API. Hence, no modification of current list but complete overwrite. I can see this would not be appropriate in other cases. I did originally prefer the presentation stuff to be handled in the XAML. Thanks for the lesson :)Esoterica
@JustinXL Thanks for getting me thinking again :)Esoterica
OK, this is the normal ListView data binding flow - You define an ObservableCollection<Premise> and bind the ItemsSource of the ListView to it. Then inside the ItemTemplate of the ListView, a TextBlock will have its Text property bind to the PremiseName. The ListViewItem is what wraps the ItemTemplate. Normally you only update the ListViewItem's style in the xaml when you want to have a different look and feel. Hope this clarifies a little!Farmhouse
Yes it does. Thanks. (I liked your app BTW)Esoterica

© 2022 - 2024 — McMap. All rights reserved.