DataGridView刷新闪烁及自定义列内容消失问题排查与解决
DataGridView刷新闪烁及自定义列内容异常问题排查与解决
问题描述
有一个接收外部数据的DataGridView,额外添加了3个自定义列,控件每3秒刷新一次。刷新时出现单元格先缩小再恢复的闪烁现象,且自定义列内容会消失后重现。移除以下代码后问题消失:
var id = row.Cells[0].Value.ToString(); var nickNameTask = Task.Run<string>(async() => await this.GetTraderName(id)); nickNameTask.Wait(); string nickName = nickNameTask.Result; row.Cells[1].Value =nickName;
问题根源
- UI线程阻塞:这段代码用
Task.Run启动异步任务后立刻调用Wait(),强制阻塞UI线程等待任务完成。DataGridView的刷新依赖UI线程,阻塞会导致控件渲染不连贯,出现闪烁、尺寸异常,同时自定义列内容因为线程卡顿无法及时更新,表现为消失后重现。 - 低效嵌套循环:外层遍历
lstUID、内层遍历所有行,相当于对每一行重复处理多次(次数等于lstUID元素数量),进一步加剧UI线程负担,放大阻塞带来的渲染问题。 - 无缓存重复请求:每次刷新都对同一ID重复调用
GetTraderName,既浪费资源,又增加UI线程等待时长。
解决方法
1. 改用异步非阻塞方式更新UI
将事件处理改为异步版本,避免阻塞UI线程:
private async void dtgvPositions_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { List<string> lstUID = this.ReplaceDownTheLine(this.txtUIDs.Text.Trim()); Dictionary<string, bool> lstUIDColor = new Dictionary<string, bool>(); for (int index = 0; index < lstUID.Count; ++index) lstUIDColor.Add(lstUID[index], index % 2 == 0); // 提前缓存所有需要的昵称,避免重复请求 Dictionary<string, string> nickNameCache = new Dictionary<string, string>(); foreach (DataGridViewRow row in dtgvPositions.Rows) { if (row.Cells[0].Value == null) continue; string id = row.Cells[0].Value.ToString(); if (!nickNameCache.ContainsKey(id)) { string nickName = await this.GetTraderName(id); nickNameCache[id] = nickName; } } // 统一更新行样式和自定义列内容 foreach (DataGridViewRow row in dtgvPositions.Rows) { if (row.Cells[0].Value == null) continue; string key = row.Cells[0].Value.ToString(); Decimal num = 0M; Decimal num1 = 0M; Decimal num2 = 0M; Decimal num3 = 0M; try { Decimal.TryParse(row.Cells[9].Value?.ToString(), out num); Decimal.TryParse(row.Cells[5].Value?.ToString(), out num1); Decimal.TryParse(row.Cells[7].Value?.ToString(), out num2); Decimal.TryParse(row.Cells[3].Value?.ToString(), out num3); // 从缓存取昵称 if (nickNameCache.TryGetValue(key, out string nickName)) { row.Cells[1].Value = nickName; } bool flag = lstUIDColor.TryGetValue(key, out flag) ? flag : false; row.DefaultCellStyle.BackColor = Color.FromArgb(24, 26, 32); row.Cells[1].Selected = false; row.Cells[1].Style.ForeColor = flag ? Color.LightSkyBlue : Color.LightPink; row.Cells[2].Value = num1 >= 0M ? "Long" : "Short"; row.Cells[2].Style.ForeColor = num1 >= 0M ? Color.FromArgb(22, 181, 93) : Color.FromArgb(194, 52, 54); row.Cells[6].Value = num3 != 0 ? (num1 * num2) / num3 : 0M; row.Cells[9].Style.ForeColor = num >= 0M ? Color.FromArgb(22, 181, 93) : Color.FromArgb(194, 52, 54); row.Cells[10].Style.ForeColor = num >= 0M ? Color.FromArgb(22, 181, 93) : Color.FromArgb(194, 52, 54); } catch { // 建议添加日志记录,便于排查异常 } } }
2. 优化循环逻辑
- 移除外层
foreach (string str in lstUID)的嵌套循环,直接遍历所有行,避免重复处理同一行。 - 提前缓存昵称,减少
GetTraderName的调用次数,提升运行效率。
3. 启用双缓冲减少闪烁
在窗体构造函数或控件初始化时开启双缓冲:
public YourForm() { InitializeComponent(); // 开启双缓冲 dtgvPositions.DoubleBuffered = true; // 若DoubleBuffered属性不可见,可通过反射设置 typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dtgvPositions, new object[] { true }); }
4. 移除不必要的Invoke
DataBindingComplete事件本身在UI线程触发,无需额外调用Invoke,直接操作控件即可。
内容的提问来源于stack exchange,提问作者zollen 123
相关产品推荐
相关产品推荐

