Reputation: 45
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.
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
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