【问题标题】:Alternating row colors on LoadingRow event in WPF DataGrid doubling colors on rows在 WPF DataGrid 中的 LoadingRow 事件上交替行颜色在行上加倍颜色
【发布时间】:2026-01-30 23:25:01
【问题描述】:

我通过 LoadingRow 事件以编程方式交替行颜色。 这样做的原因是因为我需要在某些行上指定特定的颜色,即标记为删除的行和带有修改数据的行。

这很好用,直到我在 DataGrid 中向上滚动并得到这个非常奇怪的交互,它使行颜色加倍或加倍。

向下滚动时正确显示。

我尝试使用 AlternationIndex 并将 AlternationCount 设置为 2,并使用布尔值在两者之间切换,两者都会导致完全相同的问题。

如果我没有在 LoadingRow 事件中设置它并使用 DataGrid AlternatingRowBackground,当我滚动表格时,行颜色会渗入其他行。

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"/>

【问题讨论】:

  • 可以为您的数据网格添加 xaml 吗?你在做任何虚拟化吗?除了在后面做代码之外,您是否考虑过使用数据网格行样式?我通常使用它,然后在其中使用数据触发器来更改背景颜色。它将遵循您原来的交替或默认背景颜色,并且仅在触发数据触发器时更改。在datatrigger的Binding中可以使用converter来做复杂的逻辑。
  • @JMIII 现在添加了 XML。我认为你和 J.H.建议走的路线,谢谢。

标签: wpf .net-core datagrid


【解决方案1】:

您遇到的问题是因为 DataGrid 重用了 DataGridRow 对象(可以尝试 EnableRowVirtualization="False")。

您要做的是使用样式根据它的数据/项目设置 DataGridRow 的背景。

这是一个测试应用,可以做你想做的事情。

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>

代码

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();
        }
    }
}

【讨论】:

  • LOL 我正在考虑发布一个带有数据触发器的样式示例,但你打败了我。 +1 迄今为止最好的方式。
  • @JMIII - 哈哈。我在提交之前就看到了你的评论。另外,尊重原始的 bg/alt bg——我试过了,但没有用......我确定这是因为我没有将它们设置为样式,而是在 DataGrid 上(然后你可以不要以样式覆盖)。
  • @JH 你是正确的,当你必须设置在触发器本身之后保存数据触发器的相同样式时,只有一个标准的背景设置器属性。我不知道为什么会这样,但它有效。当需要根据日志记录级别调试和突出显示不同的行时,我在我的应用程序中使用它和自定义 nlog 目标来写入 wpf 窗口
  • 谢谢,看来这是要走的路。
  • 虽然这解决了这里的主要问题,但仍然存在一个问题,即在代码中设置背景的行在滚动 RowVirtualization 表时继续保持该颜色,它们不会变回它们的交替颜色。有没有办法在进行更改时根据这些触发器刷新行样式?