You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DataGridView绘制波形图需单元格重绘才显示的解决方法

问题: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中绘制,波形图会覆盖表头、冻结列等上层元素。

我需要波形图处于底层(被表头和冻结列遮挡),同时能跨多个单元格完整显示。

核心问题

如何触发行内单元格的重绘,让波形图正常显示(达到单元格调整大小或被选中时的即时显示效果)?
尝试过通过代码调整单元格大小或选中/取消选中,但会导致绘制方法持续循环触发。


解决方案建议

  1. 精准触发局部重绘
    避免调用全局的Invalidate(),而是针对波形覆盖的单元格范围调用InvalidateCell()或InvalidateRow(),减少不必要的重绘操作:

    // 提前计算波形覆盖的起始和结束列索引
    int startCol = ...; // 波形起始列
    int endCol = ...; // 波形结束列
    // 只重绘波形覆盖的单元格
    for (int col = startCol; col <= endCol; col++)
    {
        trackEditor.InvalidateCell(col, e.RowIndex);
    }
    

    可以在seqref.WaveBitmap生成完成后执行这段代码,仅重绘需要显示波形的单元格范围。

  2. 控制RowPrePaint的默认绘制行为
    当前手动调用e.PaintCells(e.RowBounds, DataGridViewPaintParts.All)可能引发绘制层级冲突,可尝试通过设置e.PaintParts来控制默认绘制内容,比如只绘制单元格背景,再绘制波形,最后让系统处理前景内容:

    // 只绘制单元格背景,跳过前景
    e.PaintCells(e.RowBounds, DataGridViewPaintParts.Background);
    // 绘制波形逻辑...
    // 允许系统继续绘制单元格前景内容
    e.Handled = false;
    
  3. 添加重绘标志位避免循环
    当通过代码触发重绘时,加入标志位判断,防止触发无限循环:

    private bool isManualInvalidate = false;
    
    // 在波形生成完成后触发重绘
    isManualInvalidate = true;
    // 调用局部重绘方法
    trackEditor.InvalidateRow(e.RowIndex);
    isManualInvalidate = false;
    
    // 在RowPrePaint事件开头加入判断
    if (isManualInvalidate) 
    {
        e.Handled = true;
        return;
    }
    
  4. 提前预加载波形位图
    在CellFormatting事件中提前生成波形位图,确保绘制前位图已准备完成,避免绘制时才生成导致的显示延迟或不全问题。


内容的提问来源于stack exchange,提问作者CocoaMix86

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:47:19