MVVM中刷新ObservableCollection后保留DataGrid选中项的方案咨询
解决DataGrid刷新集合时丢失选中项的问题
问题根源
当前代码通过Clear()清空整个ObservableCollection后重新添加所有新项,这会导致DataGrid绑定的旧实例全部被移除,新项都是全新对象,原来的选中项实例不存在于集合中,因此选中状态丢失。
解决方案
方案1:全量刷新时恢复选中项
这种方案改动较小,适合快速实现:
- 记录刷新前的选中项标识:利用数据的唯一主键(比如
Id)保存当前选中项的标识 - 刷新集合:保持原有的全量清空、添加逻辑
- 恢复选中状态:刷新完成后,根据保存的标识找到新集合中的对应项,重新设置选中
修改后的loadDataGridMainCollection方法:
private void loadDataGridMainCollection() { // 记录当前选中项的Id(SelectedRow需实现INotifyPropertyChanged) int? selectedId = SelectedRow?.Id; // 获取新数据 DataTable dt = mySql.selectSql("SELECT id, datum, artikel, kurztext, stueckzahl, blechstaerke, rohrmasse, " + "material, kundenauftrag, ansprechpartner, abteilung, geaendertAm, twoWeeks, prio, ordnerpfad, dateiname FROM auftragstool.orders WHERE zustand = 'zu schachteln' ORDER BY import ASC, blechstaerke DESC"); // 刷新集合 DataGridMainCollection.Clear(); if (dt.Rows.Count > 0) { foreach (DataRow r in dt.Rows) { DataGridMainCollection.Add(new SchachtelEintrag { Id = Convert.ToInt32(r["id"].ToString()), Datum = Convert.ToDateTime(r["datum"].ToString()), Artikel = r["artikel"].ToString(), Kurztext = r["kurztext"].ToString(), Stueckzahl = Convert.ToInt32(r["stueckzahl"].ToString()), Blechstaerke = r["blechstaerke"].ToString(), Rohrmasse = r["rohrmasse"].ToString(), Material = r["material"].ToString(), Kundenauftrag = r["kundenauftrag"].ToString(), Ansprechpartner = r["ansprechpartner"].ToString(), Abteilung = r["abteilung"].ToString(), GeaendertAm = r["geaendertAm"].ToString(), TwoWeeks = r["twoWeeks"].ToString(), Prio = r["prio"].ToString(), Ordnerpfad = r["ordnerpfad"].ToString(), Dateiname = r["dateiname"].ToString() }); } } // 恢复选中项(如果之前有选中) if (selectedId.HasValue) { var targetItem = DataGridMainCollection.FirstOrDefault(item => item.Id == selectedId.Value); if (targetItem != null) { SelectedRow = targetItem; } } }
关键配套修改
- 修正XAML绑定:DataGrid没有
Selected属性,应使用SelectedItem绑定选中项:<DataGrid ItemsSource="{Binding DataGridMainCollection}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}"> - 确保SelectedRow实现属性通知:ViewModel中的
SelectedRow需实现INotifyPropertyChanged,才能通知UI更新选中状态:private SchachtelEintrag _selectedRow; public SchachtelEintrag SelectedRow { get => _selectedRow; set { if (_selectedRow != value) { _selectedRow = value; OnPropertyChanged(nameof(SelectedRow)); } } } // ViewModel需实现INotifyPropertyChanged接口 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
方案2:增量更新集合(更优)
避免全量清空集合,仅对新增、删除、修改的项进行操作,这样原有选中项(未被删除的情况下)会保留在集合中,无需额外恢复选中状态,同时减少UI闪烁:
- 获取新数据并转换为列表
- 对比新旧数据,执行增量操作:
- 移除旧集合中不存在于新数据的项
- 添加新数据中不存在于旧集合的项
- 更新新旧都存在的项的属性值
修改后的loadDataGridMainCollection方法:
private void loadDataGridMainCollection() { // 获取新数据并转换为列表 DataTable dt = mySql.selectSql("SELECT id, datum, artikel, kurztext, stueckzahl, blechstaerke, rohrmasse, " + "material, kundenauftrag, ansprechpartner, abteilung, geaendertAm, twoWeeks, prio, ordnerpfad, dateiname FROM auftragstool.orders WHERE zustand = 'zu schachteln' ORDER BY import ASC, blechstaerke DESC"); List<SchachtelEintrag> newItems = new List<SchachtelEintrag>(); if (dt.Rows.Count > 0) { foreach (DataRow r in dt.Rows) { newItems.Add(new SchachtelEintrag { Id = Convert.ToInt32(r["id"].ToString()), Datum = Convert.ToDateTime(r["datum"].ToString()), Artikel = r["artikel"].ToString(), Kurztext = r["kurztext"].ToString(), Stueckzahl = Convert.ToInt32(r["stueckzahl"].ToString()), Blechstaerke = r["blechstaerke"].ToString(), Rohrmasse = r["rohrmasse"].ToString(), Material = r["material"].ToString(), Kundenauftrag = r["kundenauftrag"].ToString(), Ansprechpartner = r["ansprechpartner"].ToString(), Abteilung = r["abteilung"].ToString(), GeaendertAm = r["geaendertAm"].ToString(), TwoWeeks = r["twoWeeks"].ToString(), Prio = r["prio"].ToString(), Ordnerpfad = r["ordnerpfad"].ToString(), Dateiname = r["dateiname"].ToString() }); } } // 1. 移除旧集合中不存在于新数据的项 var itemsToRemove = DataGridMainCollection.Where(oldItem => !newItems.Any(newItem => newItem.Id == oldItem.Id)).ToList(); foreach (var item in itemsToRemove) { DataGridMainCollection.Remove(item); } // 2. 添加新项或更新现有项属性 foreach (var newItem in newItems) { var existingItem = DataGridMainCollection.FirstOrDefault(oldItem => oldItem.Id == newItem.Id); if (existingItem == null) { DataGridMainCollection.Add(newItem); } else { // 更新属性,需SchachtelEintrag实现INotifyPropertyChanged existingItem.Datum = newItem.Datum; existingItem.Artikel = newItem.Artikel; existingItem.Kurztext = newItem.Kurztext; existingItem.Stueckzahl = newItem.Stueckzahl; existingItem.Blechstaerke = newItem.Blechstaerke; existingItem.Rohrmasse = newItem.Rohrmasse; existingItem.Material = newItem.Material; existingItem.Kundenauftrag = newItem.Kundenauftrag; existingItem.Ansprechpartner = newItem.Ansprechpartner; existingItem.Abteilung = newItem.Abteilung; existingItem.GeaendertAm = newItem.GeaendertAm; existingItem.TwoWeeks = newItem.TwoWeeks; existingItem.Prio = newItem.Prio; existingItem.Ordnerpfad = newItem.Ordnerpfad; existingItem.Dateiname = newItem.Dateiname; } } // 可选:处理选中项被删除的情况 if (SelectedRow != null && !DataGridMainCollection.Any(item => item.Id == SelectedRow.Id)) { SelectedRow = DataGridMainCollection.FirstOrDefault(); } }
配套要求
SchachtelEintrag类需实现INotifyPropertyChanged,确保属性更新时UI同步:
public class SchachtelEintrag : INotifyPropertyChanged { private int _id; public int Id { get => _id; set { if (_id != value) { _id = value; OnPropertyChanged(nameof(Id)); } } } private DateTime _datum; public DateTime Datum { get => _datum; set { if (_datum != value) { _datum = value; OnPropertyChanged(nameof(Datum)); } } } // 其他属性按相同模式实现,省略... public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
内容的提问来源于stack exchange,提问作者Julian2005
相关产品推荐
相关产品推荐

