You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WPF DataGrid中批量实现数据库快照差异单元格变色?

高效实现DataGrid差异单元格变色方案

方案一:自定义转换器+全局CellStyle

核心思路是通过转换器动态解析单元格绑定的属性名,对比新旧快照中对应属性的值,无需为每个列单独写DataTrigger,也不用新增大量标识属性。

  1. 实现差异对比转换器
public class PropertyDiffConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length < 3 || values[0] == null || values[1] == null)
            return false;

        var currentItem = values[0];
        var oldSnapshotItem = values[1];
        var propertyName = values[2] as string;

        if (string.IsNullOrEmpty(propertyName))
            return false;

        var currentValue = currentItem.GetType().GetProperty(propertyName)?.GetValue(currentItem);
        var oldValue = oldSnapshotItem.GetType().GetProperty(propertyName)?.GetValue(oldSnapshotItem);

        return !Equals(currentValue, oldValue);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
  1. 定义全局DataGridCell样式
    在资源字典或Window资源中定义全局样式,所有列自动复用:
<Window.Resources>
    <local:PropertyDiffConverter x:Key="PropertyDiffConverter"/>
    <Style TargetType="DataGridCell">
        <Style.Triggers>
            <DataTrigger Value="True">
                <DataTrigger.Binding>
                    <MultiBinding Converter="{StaticResource PropertyDiffConverter}">
                        <Binding /> <!-- 当前行数据对象 -->
                        <Binding Path="DataContext.OldSnapshotItems[{Binding Id}]" RelativeSource="{RelativeSource AncestorType=DataGrid}"/> <!-- 对应旧快照对象 -->
                        <Binding Path="Binding.Path.Path" RelativeSource="{RelativeSource Self}"/> <!-- 当前单元格绑定的属性名 -->
                    </MultiBinding>
                </DataTrigger.Binding>
                <Setter Property="Background" Value="Yellow"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

<DataGrid ItemsSource="{Binding CurrentItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}"/>
        <DataGridTextColumn Binding="{Binding Age}"/>
        <!-- ... 其他43个列 -->
    </DataGrid.Columns>
</DataGrid>

说明:假设每个数据对象有唯一标识Id,ViewModel中维护OldSnapshotItems字典存储旧快照对象,根据Id匹配对比。

方案二:使用附加属性统一处理

通过附加属性监听单元格加载事件,动态对比属性值并设置背景色,无需XAML中写复杂绑定。

  1. 实现附加属性
public static class DataGridCellHelper
{
    public static readonly DependencyProperty OldSnapshotSourceProperty =
        DependencyProperty.RegisterAttached("OldSnapshotSource", typeof(IDictionary<object, object>), typeof(DataGridCellHelper), new PropertyMetadata(null, OnOldSnapshotSourceChanged));

    public static void SetOldSnapshotSource(DependencyObject obj, IDictionary<object, object> value)
    {
        obj.SetValue(OldSnapshotSourceProperty, value);
    }

    public static IDictionary<object, object> GetOldSnapshotSource(DependencyObject obj)
    {
        return (IDictionary<object, object>)obj.GetValue(OldSnapshotSourceProperty);
    }

    private static void OnOldSnapshotSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGridCell cell)
        {
            cell.Loaded += Cell_Loaded;
        }
    }

    private static void Cell_Loaded(object sender, RoutedEventArgs e)
    {
        if (sender is not DataGridCell cell) return;

        var currentItem = cell.DataContext;
        if (currentItem == null) return;

        var oldSnapshotDict = GetOldSnapshotSource(cell);
        if (oldSnapshotDict == null || !oldSnapshotDict.TryGetValue(currentItem.GetType().GetProperty("Id")?.GetValue(currentItem), out var oldItem))
            return;

        var binding = cell.Content is TextBlock textBlock ? textBlock.GetBindingExpression(TextBlock.TextProperty)?.ParentBinding : null;
        if (binding?.Path == null) return;

        var propertyName = binding.Path.Path;
        var currentValue = currentItem.GetType().GetProperty(propertyName)?.GetValue(currentItem);
        var oldValue = oldItem.GetType().GetProperty(propertyName)?.GetValue(oldItem);

        if (!Equals(currentValue, oldValue))
        {
            cell.Background = Brushes.Yellow;
        }
    }
}
  1. 在XAML中应用
<DataGrid ItemsSource="{Binding CurrentItems}"
          local:DataGridCellHelper.OldSnapshotSource="{Binding OldSnapshotItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}"/>
        <DataGridTextColumn Binding="{Binding Age}"/>
        <!-- ... 其他列 -->
    </DataGrid.Columns>
</DataGrid>

说明:依赖对象的唯一标识Id匹配新旧快照对象,附加属性自动处理每个单元格的对比和样式设置。

方案三:预计算差异属性集合

在ViewModel加载数据时,通过反射预计算每个对象的差异属性名集合,然后在CellStyle中绑定判断,避免重复反射。

  1. ViewModel中处理差异集合
public class DataItemViewModel : INotifyPropertyChanged
{
    public string Name { get; set; }
    public int Age { get; set; }
    // ... 其他属性

    public HashSet<string> ModifiedProperties { get; set; } = new HashSet<string>();

    // 实现INotifyPropertyChanged接口...
}

public void LoadData()
{
    CurrentItems = GetCurrentSnapshot();
    var oldItems = GetOldSnapshot();

    foreach (var currentItem in CurrentItems)
    {
        var oldItem = oldItems.FirstOrDefault(o => o.Id == currentItem.Id);
        if (oldItem == null) continue;

        var properties = typeof(DataItem).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in properties)
        {
            var currentValue = prop.GetValue(currentItem);
            var oldValue = prop.GetValue(oldItem);
            if (!Equals(currentValue, oldValue))
            {
                currentItem.ModifiedProperties.Add(prop.Name);
            }
        }
    }
}
  1. 全局CellStyle绑定判断
<Window.Resources>
    <Style TargetType="DataGridCell">
        <Style.Triggers>
            <DataTrigger Value="True">
                <DataTrigger.Binding>
                    <MultiBinding Converter="{StaticResource IsPropertyModifiedConverter}">
                        <Binding Path="ModifiedProperties"/>
                        <Binding Path="Binding.Path.Path" RelativeSource="{RelativeSource Self}"/>
                    </MultiBinding>
                </DataTrigger.Binding>
                <Setter Property="Background" Value="Yellow"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <local:IsPropertyModifiedConverter x:Key="IsPropertyModifiedConverter"/>
</Window.Resources>

转换器代码:

public class IsPropertyModifiedConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is HashSet<string> modifiedProps && values[1] is string propName)
        {
            return modifiedProps.Contains(propName);
        }
        return false;
    }

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

内容的提问来源于stack exchange,提问作者dadou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:34:52