Jake Hayton
Jake Hayton

Reputation: 45

Alternating row colors on LoadingRow event in WPF DataGrid doubling colors on rows

I am alternating row colors programatically through the LoadingRow event. The reason for this is because I need to specify a specific color on some rows, i.e. rows marked for deletion and rows with modified data.

This works fine, until I scroll up in the DataGrid and I get this very weird interaction where it doubles or triples up the row colors.

enter image description here

It displays correctly when scrolling downward.

I have tried using AlternationIndex with AlternationCount set to 2, and using a bool to flip between, both result in the exact same issue.

If I dont set this in the LoadingRow event and use the DataGrid AlternatingRowBackground the row color bleeds into other rows as I scroll through the table.

private void dataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            // Get the DataRow corresponding to the DataGridRow that is loading.
            var item = e.Row.Item as ExpandoObject;
            if (loadedTable.ToDelete.Contains(item))
            {
                e.Row.Background = new SolidColorBrush(Colors.OrangeRed);
                return;
            }
            else if (loadedTable.Modified.Contains(loadedTable.Rows.IndexOf(item)))
            {
                e.Row.Background = new SolidColorBrush(Colors.LightYellow);
                return;
            }
            else if (e.Row.AlternationIndex == 0)
            {
                e.Row.Background = new SolidColorBrush(Colors.WhiteSmoke);
            }
            else if (e.Row.AlternationIndex == 1)
            {
                e.Row.Background = new SolidColorBrush(Colors.LightGray);
            }
        }
<DataGrid CanUserAddRows="False" GridLinesVisibility="All" VerticalGridLinesBrush="Gray" HorizontalGridLinesBrush="Gray"
                                 FontSize="15" FrozenColumnCount ="1" x:Name="xmlData" EnableRowVirtualization="True" AlternationCount="1"
                                 AlternatingRowBackground="LightGray" Background="WhiteSmoke"
                                 Grid.Column="1" Margin="0,-31,5,10" AutoGenerateColumns="False" Grid.Row="2" SelectionUnit="Cell" 
                                 PreviewKeyDown="DataGridKeyDown_Event" IsReadOnly="True" CanUserDeleteRows="True"
                                 LoadingRow="dataGrid_LoadingRow"/>

Upvotes: 1

Views: 1391

Answers (1)

J.H.
J.H.

Reputation: 4322

The problem you're having is because DataGrid reuses DataGridRow objects (could try EnableRowVirtualization="False").

What you want to do is set the DataGridRow's background based on it's data/item using styles.

Here is a test app that does what you want to do.

XAML

<Window x:Class="WpfApp9.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp9"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:VM />
</Window.DataContext>
<Grid>
    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}" AlternationCount="2">
        <DataGrid.Resources>
            <!-- Converter used to convert the DataRow's Item and the VM.ToDelete list to bool (true = it is deleted) -->
            <local:IsDeletedConverter x:Key="IsDeletedConverter" />
        </DataGrid.Resources>
        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow">
                <Style.Triggers>
                    <!-- Setup the background color for normal rows using AlternationIndex -->
                    <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                        <Setter Property="Background" Value="WhiteSmoke" />
                    </Trigger>
                    <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                        <Setter Property="Background" Value="LightGray" />
                    </Trigger>
                    <!-- Override the above background colors if it is in the deleted list - NOTE: these styles are processed in order, make sure this is after the above triggers -->
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <MultiBinding Converter="{StaticResource IsDeletedConverter}">
                                <!-- This is the DataContext of the DataGridRow - the item (ExpandoObject) we will check for in the deleted list -->
                                <Binding />
                                <!-- Need the deleted list, which is in VM -->
                                <Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext" />
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <DataTrigger.Setters>
                            <Setter Property="Background" Value="OrangeRed" />
                        </DataTrigger.Setters>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </DataGrid.RowStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Col1" Binding="{Binding Col1}" />
            <DataGridTextColumn Header="Col2" Binding="{Binding Col2}" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

CODE

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApp9
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class VM
    {
        public List<System.Dynamic.ExpandoObject> Items { get; set; }
        public List<System.Dynamic.ExpandoObject> ToDelete { get; set; }

        public VM()
        {
            Items = new List<System.Dynamic.ExpandoObject>();
            ToDelete = new List<System.Dynamic.ExpandoObject>();

            for (int i = 0; i < 1000; i++)
            {
                var eo = new System.Dynamic.ExpandoObject();
                var d = eo as IDictionary<string, object>;
                d["Col1"] = $"String {i}";
                d["Col2"] = i;
                Items.Add(eo);

                // Add some items to ToDelete list
                if (i % 10 == 0)
                {
                    ToDelete.Add(eo);
                }
            }
        }
    }

    public class IsDeletedConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length != 2)
                throw new ArgumentException($"IsDeletedConverter is expecting 2 values but got {values.Length} values!", nameof(values));

            if (values[0] is System.Dynamic.ExpandoObject eo && values[1] is VM vm)
            {
                if (vm.ToDelete.Contains(eo))
                    return true;
            }
            return false;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Upvotes: 2

Related Questions