WPF DataGrid列ActualWidth异常 等待ObservableCollection更新方法
核心原因
WPF DataGrid的Auto宽度列实际渲染宽度计算,属于UI渲染队列中低优先级任务。你在数据更新后同步调用UpdateLayout()、Items.Refresh(),或是用Thread.Sleep()阻塞线程,都无法等待列宽计算完成——Thread.Sleep()会直接卡住UI线程,阻塞整个布局渲染流程,此时读取到的ActualWidth是未计算完成的旧值/默认值,自然会出现错位。调试模式下执行速度慢,列宽计算有足够时间跑完,所以能拿到正确结果。
可落地解决方案
方案1:Dispatcher排队等待渲染完成(推荐)
不要在更新ObservableCollection的同步代码块中直接读取列宽,将生成TextBox的逻辑投递到WPF Dispatcher队列的最低优先级,等所有布局、渲染、交互事件全部处理完成后再执行,此时拿到的ActualWidth是完全准确的。
修正后的代码如下:
// 1. 先完成ObservableCollection的数据填充、ItemsSource绑定操作 popup.DataGridSelectedObjects.ItemsSource = yourRevitSelectedElementCollection; popup.DataGridSelectedObjects.UpdateLayout(); // 2. 将列宽读取、TextBox生成逻辑投递到ContextIdle优先级队列 popup.Dispatcher.BeginInvoke(new Action(() => { StackRegexPanel.Children.Clear(); foreach (DataGridColumn dc in popup.DataGridSelectedObjects.Columns) { AddRegexItemToStackpanel(dc.Header.ToString(), dc); } }), System.Windows.Threading.DispatcherPriority.ContextIdle); // 原有生成TextBox的方法不需要修改 public void AddRegexItemToStackpanel(string parameterName, DataGridColumn dc) { TextBox printTextBlock = new TextBox(); printTextBlock.Width = dc.ActualWidth; printTextBlock.Margin = new Thickness(0.5, 0, 0, 0); printTextBlock.Name = dc.Header.ToString().Replace("__", "_"); StackRegexPanel.Children.Add(printTextBlock); }
方案2:监听LayoutUpdated事件兜底(适配极端场景)
如果方案1在个别场景下仍有偏差,可以给DataGrid注册一次性的LayoutUpdated事件,等所有列宽计算完成(所有列的ActualWidth大于0)时再生成TextBox,执行完成后立刻注销事件避免重复触发:
// 在更新ItemsSource的位置注册事件 popup.DataGridSelectedObjects.LayoutUpdated += GenerateMatchWidthTextBoxes; private void GenerateMatchWidthTextBoxes(object sender, EventArgs e) { DataGrid dg = sender as DataGrid; // 校验所有列宽已完成计算 if (dg.Columns.Any(col => col.ActualWidth <= double.Epsilon)) { return; } // 列宽计算完成,执行生成逻辑 StackRegexPanel.Children.Clear(); foreach (DataGridColumn dc in dg.Columns) { AddRegexItemToStackpanel(dc.Header.ToString(), dc); } // 注销事件,避免重复触发 dg.LayoutUpdated -= GenerateMatchWidthTextBoxes; }
注意事项
- 禁止在UI线程使用
Thread.Sleep()等待UI更新,该方法会阻塞UI线程,让布局、渲染流程完全暂停,加再长的延迟都等不到列宽计算完成。 - 如果DataGrid开启了列虚拟化,被滚出视口、未实际渲染的列可能拿不到准确宽度,性能允许的情况下可以临时将
EnableColumnVirtualization设为false,等TextBox生成完成后再恢复。 - Revit插件场景下所有WPF UI操作必须在Revit主UI线程执行,不要在后台线程中操作控件,避免触发跨线程异常。
内容的提问来源于stack exchange,提问作者codenox
相关产品推荐
相关产品推荐

