如何无缝更新绑定Dataset的仪表板DataGrid,解决运行时刷新闪烁问题
问题描述
我有一个用于仪表板展示的DataGrid,包含两列数据,行数由用户在启动前选择,运行时数据最高每秒更新1次。
当前遇到的问题是:运行时通过修改关联的Dataset来更新DataGrid内容时会产生明显闪烁。
第一种实现方案代码如下:
dtsWatch.Tables[0].Rows.Clear();// <----- 此处触发闪烁 foreach (...required keyword...) { ... dtsWatch.Tables[0].Rows.Add(label, value);// <---- 此处触发闪烁 ... } dtgWatch.ItemsSource = dtsWatch.Tables[0].DefaultView;// <---- 此处触发闪烁
第二种方案基于「将DataGrid与Dataset解绑,先在临时数据集上完成修改再重新绑定」的思路实现,代码如下:
dtvTemp = (DataView)dtgWatch.ItemsSource;// <--- 从datagrid克隆临时数据集 dtgWatch.ItemsSource = dtvTemp;// <------------- 将datagrid绑定到临时数据集 dtsWatch.Tables[0].Rows.Clear();// <------------ 重置已解绑的主数据集 foreach (...required keyword...) { ... dtsWatch.Tables[0].Rows.Add(label, value);// <--- 给已解绑的主数据集新增行 ... } dtgWatch.ItemsSource = dtsWatch.Tables[0].DefaultView;// <---- 重新绑定主数据集
我采用上述方案是因为WPF中没有找到更新Dataset时冻结DataGrid刷新的相关指令,但第二种方案也完全没有减轻闪烁问题。请问针对DataGrid更新场景,还有没有其他更优的实现方案?
解决方案
方案1:用控件生命周期方法挂起批量更新期间的渲染
WPF的DataGrid自带BeginInit()/EndInit()方法,可以在批量修改数据前挂起控件的布局和渲染刷新,所有数据修改完成后再一次性刷新界面,避免每次修改行数据触发逐次重绘:
// 开始批量更新前挂起渲染 dtgWatch.BeginInit(); dtsWatch.Tables[0].Rows.Clear(); foreach (...required keyword...) { dtsWatch.Tables[0].Rows.Add(label, value); } dtgWatch.ItemsSource = dtsWatch.Tables[0].DefaultView; // 全部修改完成后恢复渲染,一次性刷新 dtgWatch.EndInit();
方案2:避免重复增删行,直接更新已有行数据
你的场景中行数是启动前固定的,完全不需要每次更新都清空、新增行,直接修改已有行的单元格值即可,大幅减少布局重算开销:
int rowIdx = 0; foreach (...required keyword...) { // 直接修改对应行的两个列值,不触发行增删的重绘逻辑 dtsWatch.Tables[0].Rows[rowIdx][0] = label; dtsWatch.Tables[0].Rows[rowIdx][1] = value; rowIdx++; }
方案3:优化DataGrid渲染配置
可以在XAML中给DataGrid添加以下配置降低重绘开销:
<DataGrid x:Name="dtgWatch" CacheMode="BitmapCache" EnableColumnVirtualization="False" EnableRowVirtualization="False" ScrollViewer.CanContentScroll="False"> <!-- 列配置 --> </DataGrid>
注:行数量较少时关闭虚拟化可以减少虚拟化切换带来的额外开销,行数超过50行不建议关闭
方案4:替换绑定源为强类型集合
相比DataSet的粗粒度通知,用实现了INotifyPropertyChanged的强类型类配合ObservableCollection<T>作为绑定源,更新单个属性只会触发对应单元格的刷新,性能提升最明显:
// 定义行数据类 public class WatchItem : INotifyPropertyChanged { private string _label; public string Label { get => _label; set { _label = value; OnPropertyChanged(); } } private string _value; public string Value { get => _value; set { _value = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // 初始化绑定(启动时执行一次即可) ObservableCollection<WatchItem> WatchItems = new ObservableCollection<WatchItem>(); dtgWatch.ItemsSource = WatchItems; // 运行时更新逻辑,无需修改绑定源 int rowIdx = 0; foreach (...required keyword...) { WatchItems[rowIdx].Label = label; WatchItems[rowIdx].Value = value; rowIdx++; }
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

