Wpf DataGrid排序或CRUD操作致复选框状态重置的解决方法
解决WPF DataGrid复选框选中状态在排序/CRUD后重置的问题
核心原因
每次执行DgTest.ItemsSource = dc.TestTable.ToList()时,都是从数据库获取全新的实体对象集合替换原有数据源。原来的复选框选中状态要么存储在UI控件中,要么绑定到临时属性,新生成的实体对象不包含这些状态,导致选中状态丢失。
解决方案1:用ViewModel包装实体(推荐)
将选中状态从业务实体剥离,放到独立的ViewModel中,同时维护一个字典记录每个实体的选中状态,刷新数据时通过实体唯一标识恢复状态。
步骤1:创建ViewModel类
using System.ComponentModel; using System.Runtime.CompilerServices; public class TestItemViewModel : INotifyPropertyChanged { private readonly TestTable _entity; private bool _isSelected; // 全局字典,跨刷新保存选中状态 private static Dictionary<int, bool> _selectionTracker = new Dictionary<int, bool>(); // 实体唯一标识(比如主键Id) public int Id => _entity.Id; // 转发实体的业务属性(用于计算平均值的字段) public decimal TargetValue => _entity.TargetValue; public bool IsSelected { get => _isSelected; set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(); // 更新全局状态字典 _selectionTracker[Id] = value; // 触发平均值计算逻辑 CalculateSelectedAverage(); } } } public TestItemViewModel(TestTable entity) { _entity = entity; // 初始化时读取之前的选中状态,默认未选中 _selectionTracker.TryGetValue(entity.Id, out _isSelected); } // 平均值计算示例 private void CalculateSelectedAverage() { var window = Application.Current.MainWindow as YourWindow; if (window == null) return; var selectedValues = window.DgTest.Items.Cast<TestItemViewModel>() .Where(vm => vm.IsSelected) .Select(vm => vm.TargetValue); window.TxtAverage.Text = selectedValues.Any() ? selectedValues.Average().ToString("F2") : "0.00"; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:修改DataGrid刷新逻辑
private void RefreshDataGrid() { using var dc = new DataContextTest(); var newEntities = dc.TestTable.ToList(); // 将新实体包装成ViewModel,自动恢复选中状态 var viewModelList = newEntities.Select(e => new TestItemViewModel(e)).ToList(); DgTest.ItemsSource = viewModelList; }
步骤3:修改DataGrid列绑定
在XAML中,复选框列绑定到ViewModel的IsSelected属性:
<DataGridCheckBoxColumn Header="选中" Binding="{Binding IsSelected}" />
解决方案2:给实体添加非映射的临时属性
如果不想引入ViewModel,可以在实体类中添加一个不映射到数据库的IsSelected属性,刷新数据时只更新实体的业务属性,不替换整个集合。
步骤1:修改实体类
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel; using System.Runtime.CompilerServices; public class TestTable : INotifyPropertyChanged { private decimal _targetValue; public int Id { get; set; } public decimal TargetValue { get => _targetValue; set { if (_targetValue != value) { _targetValue = value; OnPropertyChanged(); } } } // 标记为非映射,不会存入数据库 [NotMapped] public bool IsSelected { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:用ObservableCollection维护ItemsSource
// 页面级字段,初始化时绑定到DataGrid private ObservableCollection<TestTable> _testItems = new ObservableCollection<TestTable>(); public YourWindow() { InitializeComponent(); DgTest.ItemsSource = _testItems; RefreshDataGrid(); } private void RefreshDataGrid() { using var dc = new DataContextTest(); var newEntities = dc.TestTable.ToList(); var existingIds = _testItems.Select(item => item.Id).ToHashSet(); // 移除新数据中不存在的项 var itemsToRemove = _testItems.Where(item => !newEntities.Any(e => e.Id == item.Id)).ToList(); foreach (var item in itemsToRemove) { _testItems.Remove(item); } // 新增或更新现有项(保留IsSelected状态) foreach (var newEntity in newEntities) { var existingItem = _testItems.FirstOrDefault(item => item.Id == newEntity.Id); if (existingItem == null) { _testItems.Add(newEntity); } else { // 只更新业务属性,保留选中状态 existingItem.TargetValue = newEntity.TargetValue; // 其他业务属性同理更新 } } }
方案优势
- 两种方案都无需将选中状态存入数据库,避免了大量数据的更新操作,性能远优于原方案。
- ViewModel方案分离了UI状态与业务实体,代码更清晰、扩展性更强;临时属性方案更简洁,适合简单业务场景。
内容的提问来源于stack exchange,提问作者user8245153
相关产品推荐
相关产品推荐

