WPF DataGrid的ItemContainerGenerator状态为NotStarted的原因及解决方法
WPF DataGrid ItemContainerGenerator状态问题分析与解决
问题描述
我有一个WPF DataGrid,当滚动到顶部或数据量不足无需滚动时,ItemContainerGenerator.Status处于NotStarted状态,仅在滚动时状态才会改变。请问这是什么原因?如何强制ItemContainerGenerator.Status切换为就绪状态?
相关代码片段
ItemContainerGenerator状态变更事件处理
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if ((sender as ItemContainerGenerator).Status == GeneratorStatus.ContainerGenerated) { if (obj != null) { Win.Dispatcher.BeginInvoke(new Action(() => { Win.SelectedItem = obj; Win.ScrollIntoView(obj); }), DispatcherPriority.Render); } } }
窗口打开逻辑(关联ShowAsync)
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) { var record = GetRecord(); //从列表框获取要在DataGrid中选中的记录 ToolWindowPane win = Package.FindToolWindow(typeof(Pane), key, false); if (win!= null) { MyToolWindow.MyDictionary[record.Name].SelectRecord(record.Name); return MyToolWindow.ShowAsync(key, false); } ///...首次打开窗口等其他代码 }
窗口类核心代码
public MyToolWindow() //构造函数 { SelectRecord(record); } private void SelectRecord(string record) { var obj = LookForRecordToSelect(record); datagrid.SelectedItem = obj; datagrid.ScrollIntoView(obj); }
补充:首次选择能生效可能是因为逻辑定义在构造函数中。
原因分析
WPF的ItemContainerGenerator默认采用虚拟化加载机制:只有当Item进入可视区域时,才会生成对应的UI容器(比如DataGridRow)。当DataGrid处于顶部或数据量少到无需滚动时,所有Item可能还没触发容器生成逻辑,导致Status停留在NotStarted;而滚动操作会触发可视区域变化,强制生成可见Item的容器,从而改变Status。
另外,非首次打开窗口时调用SelectRecord的时机过早——此时DataGrid还没完成布局渲染,容器生成逻辑尚未启动,所以ItemContainerGenerator无法进入就绪状态。
解决方法
方法1:强制触发布局与容器生成
通过强制DataGrid完成布局,再手动触发容器生成逻辑:
private void SelectRecord(string record) { var obj = LookForRecordToSelect(record); // 强制完成布局测量与排列 datagrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); datagrid.Arrange(new Rect(datagrid.DesiredSize)); // 监听容器生成状态,完成后执行选择 if (datagrid.ItemContainerGenerator.Status != GeneratorStatus.ContainerGenerated) { void OnStatusChanged(object s, EventArgs args) { var generator = s as ItemContainerGenerator; if (generator.Status == GeneratorStatus.ContainerGenerated) { datagrid.SelectedItem = obj; datagrid.ScrollIntoView(obj); generator.StatusChanged -= OnStatusChanged; // 移除监听避免重复触发 } } datagrid.ItemContainerGenerator.StatusChanged += OnStatusChanged; // 触发布局更新,启动容器生成 datagrid.UpdateLayout(); } else { // 容器已就绪,直接执行选择 datagrid.SelectedItem = obj; datagrid.ScrollIntoView(obj); } }
方法2:延迟执行选择逻辑
非首次打开窗口时,先等待窗口显示完成,再通过Dispatcher以渲染优先级执行选择操作,确保DataGrid已完成初始化:
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) { var record = GetRecord(); ToolWindowPane win = Package.FindToolWindow(typeof(Pane), key, false); if (win!= null) { await win.ShowAsync(key, false); // 等待窗口显示完成 // 延迟到渲染阶段执行选择 await win.Dispatcher.BeginInvoke(new Action(() => { MyToolWindow.MyDictionary[record.Name].SelectRecord(record.Name); }), DispatcherPriority.Render); return; } ///...首次打开窗口等其他代码 }
方法3:关闭虚拟化(仅小数据量场景)
如果数据量不大,可以关闭DataGrid的虚拟化,让所有Item容器一次性生成,ItemContainerGenerator会快速进入就绪状态:
<DataGrid x:Name="datagrid" VirtualizingStackPanel.IsVirtualizing="False"/>
注意:此方法会增加内存占用,大数据量场景不推荐使用。
内容的提问来源于stack exchange,提问作者coala
相关产品推荐
相关产品推荐

