DataGridView绘制波形图需单元格重绘才显示的解决方法
我正在开发一款音序器应用,需要在DataGridView的行上绘制音频样本的波形图,但波形图要等到覆盖的单元格被选中、滚出屏幕或调整大小后才会完全显示。
现有实现代码
我在RowPrePaint事件中完成图像绘制,代码如下:
private void trackEditor_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e) { //只预绘制PLAY SAMPLE行,只有这些行需要显示波形 if (SequencerObjects[e.RowIndex].category == "PLAY SAMPLE") { //PLAY SAMPLE单元格会被排除在CellPainting()之外,所以先在这里绘制单元格 //要在绘制波形前完成,否则波形会被覆盖 RowPrePainting = true; e.PaintCells(e.RowBounds, DataGridViewPaintParts.All); RowPrePainting = false; //这个检查放在这里而不是第一个if里,这样即使行没有设置值,单元格仍然会被绘制 if (!SequencerObjects[e.RowIndex].data_points.Any(x => x.value != null)) return; //绘制位图时用到的参数 int offsetportion = (trackEditor.Columns[3].Width - trackEditor.FirstDisplayedScrollingColumnHiddenWidth) + trackEditor.RowHeadersWidth + (trackEditor.Columns[0].Width * 3); int columnindex = trackEditor.FirstDisplayedScrollingColumnIndex - FrozenColumnOffset + 1; Sequencer_Object seqref = SequencerObjects[e.RowIndex]; SampleData samp = TCLE.ProjectSamples.FirstOrDefault(x => x.obj_name == seqref.obj_name); //如果samp为空,就没有音频文件可以处理成波形 if (samp == null) { return; } if (seqref.WaveBitmap == null) { ///这里是生成位图的数学计算和创建代码 /// } //位图创建完成后开始绘制 foreach (SeqDataPoint sdp in seqref.data_points.Where(x => x.value != null)) { //如果图像起始位置在屏幕外,就不绘制 if (sdp.beat > columnindex + trackEditor.DisplayedColumnCount(true) && sdp.beat + samp.beats < columnindex) continue; //根据当前激活节拍计算波形的水平偏移量 e.Graphics.DrawImage(seqref.WaveBitmap, ((sdp.beat - columnindex) * cellwidth) + offsetportion, e.RowBounds.Top + 2); } } }
已知绘制逻辑
DataGridView的绘制触发顺序为:RowPrePaint -> CellPainting -> RowPostPaint
当前困境
由于波形图需要跨多个单元格,只能选择在RowPrePaint中绘制:
- 若在
CellPainting中绘制,图形对象仅对应单个单元格范围,无法实现跨单元格效果; - 若在
RowPostPaint中绘制,波形图会覆盖表头、冻结列等上层元素。
我需要波形图处于底层(被表头和冻结列遮挡),同时能跨多个单元格完整显示。
核心问题
如何触发行内单元格的重绘,让波形图正常显示(达到单元格调整大小或被选中时的即时显示效果)?
尝试过通过代码调整单元格大小或选中/取消选中,但会导致绘制方法持续循环触发。
解决方案建议
精准触发局部重绘
避免调用全局的Invalidate(),而是针对波形覆盖的单元格范围调用InvalidateCell()或InvalidateRow(),减少不必要的重绘操作:// 提前计算波形覆盖的起始和结束列索引 int startCol = ...; // 波形起始列 int endCol = ...; // 波形结束列 // 只重绘波形覆盖的单元格 for (int col = startCol; col <= endCol; col++) { trackEditor.InvalidateCell(col, e.RowIndex); }可以在
seqref.WaveBitmap生成完成后执行这段代码,仅重绘需要显示波形的单元格范围。控制
RowPrePaint的默认绘制行为
当前手动调用e.PaintCells(e.RowBounds, DataGridViewPaintParts.All)可能引发绘制层级冲突,可尝试通过设置e.PaintParts来控制默认绘制内容,比如只绘制单元格背景,再绘制波形,最后让系统处理前景内容:// 只绘制单元格背景,跳过前景 e.PaintCells(e.RowBounds, DataGridViewPaintParts.Background); // 绘制波形逻辑... // 允许系统继续绘制单元格前景内容 e.Handled = false;添加重绘标志位避免循环
当通过代码触发重绘时,加入标志位判断,防止触发无限循环:private bool isManualInvalidate = false; // 在波形生成完成后触发重绘 isManualInvalidate = true; // 调用局部重绘方法 trackEditor.InvalidateRow(e.RowIndex); isManualInvalidate = false; // 在RowPrePaint事件开头加入判断 if (isManualInvalidate) { e.Handled = true; return; }提前预加载波形位图
在CellFormatting事件中提前生成波形位图,确保绘制前位图已准备完成,避免绘制时才生成导致的显示延迟或不全问题。
内容的提问来源于stack exchange,提问作者CocoaMix86

