ItemsControl与数据源不一致:WPF DataGrid操作崩溃问题
这个问题我之前也碰到过,典型的WPF集合绑定没做好变更通知导致的!咱们一步步来拆解和解决:
问题根源
你抛出的System.InvalidOperationException异常,核心原因是你的数据源没有实现INotifyCollectionChanged接口。WPF的ItemsControl(包括DataGrid)依赖这个接口来同步UI和数据源的状态:当集合增删元素时,需要通知UI更新。如果用普通的ICollection或者List,修改集合后UI不会收到通知,多次操作后UI的内部状态和实际数据源就会脱节,最终触发校验崩溃。
具体解决方案
1. 把ViewModel的集合换成ObservableCollection
ObservableCollection是WPF专门设计的集合类型,自动实现了INotifyCollectionChanged和INotifyPropertyChanged,能实时通知UI集合变化。
修改ViewModel里的集合定义:
// 原来的ICollection替换成ObservableCollection private ObservableCollection<RentalItem> _allItems; private ObservableCollection<RentalItem> _addedItems; public ObservableCollection<RentalItem> AllItems { get => _allItems; set => SetProperty(ref _allItems, value); // 假设你的ViewModelBase有SetProperty实现 } public ObservableCollection<RentalItem> AddedItems { get => _addedItems; set => SetProperty(ref _addedItems, value); }
2. 加载数据时初始化ObservableCollection
从数据库加载数据后,要把结果转换成ObservableCollection,而不是直接用数据库返回的普通集合:
// 示例:从服务获取数据后初始化 var availableItems = itemService.GetAvailableRentalItems(); // 假设返回IEnumerable<RentalItem> AllItems = new ObservableCollection<RentalItem>(availableItems); AddedItems = new ObservableCollection<RentalItem>();
3. 修正Add/Remove命令的实现
确保命令里直接操作ObservableCollection的实例,这样每一次增删都会自动通知UI:
// AddItemCommand示例(用RelayCommand,假设你用的是MVVM Light或类似框架) public ICommand AddItemCommand => new RelayCommand<int>(itemId => { var targetItem = AllItems.FirstOrDefault(item => item.Id == itemId); if (targetItem != null) { AllItems.Remove(targetItem); AddedItems.Add(targetItem); } }); // RemoveItemCommand示例 public ICommand RemoveItemCommand => new RelayCommand<int>(itemId => { var targetItem = AddedItems.FirstOrDefault(item => item.Id == itemId); if (targetItem != null) { AddedItems.Remove(targetItem); AllItems.Add(targetItem); } });
4. 移除View里重复的Click事件
你的Add按钮同时绑定了Command和Click="OnAddButton",这会导致操作被触发两次,加剧状态不一致的问题。直接删掉Click事件,只用Command:
<!-- 修改前 --> <Button Content="Add" Command="{Binding DataContext.AddItemCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" CommandParameter="{Binding Id}" Click="OnAddButton" > <!-- 修改后 --> <Button Content="Add" Command="{Binding DataContext.AddItemCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" CommandParameter="{Binding Id}" >
为什么这样能解决问题?
当你用ObservableCollection时,每次调用Add/Remove都会触发CollectionChanged事件,WPF的DataGrid会监听这个事件,实时更新UI的Items集合,保证UI和数据源的状态完全同步,就不会再出现“累计计数与实际计数不符”的异常了。
内容的提问来源于stack exchange,提问作者Iluvpresident

