如何在WPF DataGrid中批量实现数据库快照差异单元格变色?
高效实现DataGrid差异单元格变色方案
方案一:自定义转换器+全局CellStyle
核心思路是通过转换器动态解析单元格绑定的属性名,对比新旧快照中对应属性的值,无需为每个列单独写DataTrigger,也不用新增大量标识属性。
- 实现差异对比转换器
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(); } }
- 定义全局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中写复杂绑定。
- 实现附加属性
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; } } }
- 在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中绑定判断,避免重复反射。
- 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); } } } }
- 全局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
相关产品推荐
相关产品推荐

