数据虚拟化:通知DataGrid更新时抛出「指定方法不支持」异常
问题解答:WPF DataGrid虚拟异步数据源异常与重复访问问题
问题背景
要构建一个从异步源获取数据的随机访问虚拟数据源,目前正在开发基础验证原型——从文件读取数据并在DataGrid中展示,为此自定义了实现IList和INotifyCollectionChanged接口的BufferRandomAccessVirtualiser集合类。
相关代码
XAML中的DataGrid定义
<controls:DataGrid controls:DockPanel.Dock="Right" x:Name="HexDump" ItemsSource="{Binding DataRows, Mode=OneWay}" AutoGenerateColumns="false" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" DataFetchSize="32"> <controls:DataGrid.Columns> <controls:DataGridTextColumn Header="Addr" Binding="{Binding startAdr, Converter={StaticResource x4Converter}}"/> <controls:DataGridTextColumn Header="Bytes" Binding="{Binding data, Converter={StaticResource x2Converter}}"/> <controls:DataGridTextColumn Header="Chars" Binding="{Binding data, Converter={StaticResource cConverter}}"/> </controls:DataGrid.Columns> </controls:DataGrid>
BufferRandomAccessVirtualiser部分实现
public class BufferRandomAccessVirtualiser : IList, INotifyCollectionChanged { // INotifyCollectionChanged impl public event NotifyCollectionChangedEventHandler CollectionChanged; // IList impl Dictionary<int, BufferedBlock> tempBlocks = new Dictionary<int, BufferedBlock>(); public BufferedBlock this[int index] { get { if (index >= Count) throw new IndexOutOfRangeException(); if (tempBlocks.ContainsKey(index)) { Debug.Write(index); Debug.Write("& "); return tempBlocks[index]; } Debug.Write(index); Debug.Write("? "); var oldItem = tempBlocks[index] = new BufferedBlock(); // placeholder loadRowsTask.ContinueWith(rowsResult => { var row = index % rowsPerPage; var newItem = new BufferedBlock(rowsResult[row]); Debug.Write(index); Debug.Write("<"); tempBlocks[index] = newItem; CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, index)); Debug.Write("> "); }); return tempBlocks[index]; } set => throw new NotImplementedException(); } // other parts of IList impl - just boilerplate really }
遇到的问题
- 初始化阶段,索引0的元素被反复调用,调试输出显示多次访问;
- 触发
CollectionChanged的Replace动作时,DataGrid调用索引器验证新值后抛出System.NotSupportedException(提示「指定方法不支持」),移除所有列后问题仍存在,已排除绑定转换器问题。
一、System.NotSupportedException异常原因
核心问题
- IList接口实现不全:DataGrid在处理
Replace类型的集合变更通知时,会调用IList的Contains、IndexOf等方法做验证。如果这些方法是抛出NotImplementedException的样板代码,就会触发该异常。 - 跨线程访问问题:异步任务的
ContinueWith默认在非UI线程执行,直接触发CollectionChanged事件属于跨线程操作UI控件,会引发隐藏异常,也可能间接导致不支持的操作提示。
修复方案
- 完善IList核心方法:至少实现
Contains和IndexOf方法,返回合理值而非抛出异常:public bool Contains(object value) { if (value is BufferedBlock block) { return tempBlocks.Values.Contains(block); } return false; } public int IndexOf(object value) { if (value is BufferedBlock block) { foreach (var pair in tempBlocks) { if (pair.Value == block) return pair.Key; } } return -1; } - 切换到UI线程触发变更事件:使用
Dispatcher确保CollectionChanged在UI线程执行:loadRowsTask.ContinueWith(rowsResult => { var row = index % rowsPerPage; var newItem = new BufferedBlock(rowsResult[row]); Application.Current.Dispatcher.Invoke(() => { tempBlocks[index] = newItem; CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, index)); }); });
二、初始化时反复调用索引0的原因
核心原因
- DataGrid布局测量需求:初始化时,DataGrid会多次访问索引0的元素——第一次用于测量行高,第二次用于验证数据源有效性;
- 占位符无属性变更通知:返回的空
BufferedBlock未实现INotifyPropertyChanged,DataGrid无法感知到后续数据更新,会重复调用索引器确认值是否变化; - 异步加载时序问题:首次返回占位符后,若变更事件触发不及时,DataGrid会再次请求索引0的数据。
修复方案
- 让
BufferedBlock实现INotifyPropertyChanged接口,异步加载完成后更新属性并触发PropertyChanged事件,而非直接替换整个对象。这样DataGrid会通过属性变更更新UI,无需反复调用索引器,也能避免集合Replace动作带来的验证问题。
额外注意事项
- 你使用的DataGrid带有
DataFetchSize属性,属于自定义虚拟化DataGrid(如WPF Toolkit版本),需确认控件文档中对虚拟数据源的要求,是否需要额外实现ICollectionView等接口; - 在索引器中添加加载状态标记,避免同一索引被多次触发异步加载逻辑,防止重复请求数据。
内容的提问来源于stack exchange,提问作者Jules May
相关产品推荐
相关产品推荐

