PAYATHON 2026

DataGridView 水平滚动时自绘竖线消失,如何修复?

数据阿明

结论

将自定义绘制限制在当前单元格内:先绘制默认内容,再绘制竖线,最后设置 e.Handled = true。

原代码有两处明确的问题:数据单元格分支没有设置 e.Handled,处理每个单元格时还尝试绘制贯穿整个控件高度的竖线。绘制结果因此会受到后续默认绘制和滚动时局部重绘范围的影响。仅凭这段代码,无法确认运行环境中的全部触发因素,但可以先修正这两处。

原代码为什么容易出现异常

自绘竖线之后仍有后续绘制

数据单元格分支调用 DrawLine 后,会执行方法末尾的 e.PaintContent(cr)。由于该分支没有设置 e.Handled = true,事件返回后,DataGridView 还会继续执行默认绘制。

白色竖线画完后,后续的背景或边框绘制可能将它覆盖。列标题分支虽然设置了 e.Handled,但只对当前这次事件有效,不会影响其他单元格。

单元格事件中使用了整个控件的坐标范围

下面的代码从控件顶部画到控件底部:

new Point(e.CellBounds.Right - 1, 0),
new Point(e.CellBounds.Right - 1, grid.Height)

CellPainting 按单元格触发,绘制区域也会受到裁剪限制,因此这段代码不能保证整条竖线都会被画出来。

水平滚动时,控件可能只重绘需要更新的区域。即使初次显示正常,也不能证明这种跨单元格的绘制方式可靠。竖线应从当前单元格的 Top 画到 Bottom - 1。

修改示例

下面的处理方式保留深色列标题、青色标题底线和白色数据列分隔线。数据单元格先完成默认绘制,再补画自定义竖线。

private void dgv_CellPainting(
    object sender,
    DataGridViewCellPaintingEventArgs e)
{
    // 行标题及左上角单元格继续使用默认绘制。
    if (e.ColumnIndex < 0 || e.RowIndex < -1)
    {
        return;
    }

    Rectangle bounds = e.CellBounds;
    Rectangle clip = Rectangle.Intersect(bounds, e.ClipBounds);

    if (clip.IsEmpty)
    {
        return;
    }

    System.Drawing.Drawing2D.GraphicsState state = e.Graphics.Save();

    try
    {
        e.Graphics.SetClip(
            clip,
            System.Drawing.Drawing2D.CombineMode.Intersect);

        if (e.RowIndex == -1)
        {
            using (Brush background =
                new SolidBrush(Color.FromArgb(3, 10, 60)))
            {
                e.Graphics.FillRectangle(background, bounds);
            }

            // 保留标题文字等前景内容,避免默认背景覆盖自定义颜色。
            e.Paint(clip, DataGridViewPaintParts.ContentForeground);

            using (Pen bottomLine =
                new Pen(Color.FromArgb(0, 255, 255), 2))
            {
                int y = bounds.Bottom - 2;

                e.Graphics.DrawLine(
                    bottomLine,
                    bounds.Left,
                    y,
                    bounds.Right - 1,
                    y);
            }
        }
        else
        {
            // 先绘制默认背景、内容和边框。
            e.Paint(clip, e.PaintParts);

            // 最后在当前单元格右边缘补画竖线。
            using (Pen rightLine = new Pen(Color.White, 1))
            {
                int x = bounds.Right - 1;

                e.Graphics.DrawLine(
                    rightLine,
                    x,
                    bounds.Top,
                    x,
                    bounds.Bottom - 1);
            }
        }

        e.Handled = true;
    }
    finally
    {
        e.Graphics.Restore(state);
    }
}

这段代码需要 System.Drawing 和 System.Windows.Forms 命名空间。替换原事件处理方法后,应移除原方法末尾无条件执行的 e.PaintContent(cr),避免再次绘制内容。

绘制时需要注意什么

e.CellBounds 表示当前单元格的完整矩形,e.ClipBounds 表示当前绘制需要考虑的裁剪范围。水平滚动后,单元格可能只有一部分可见,也可能只需要重绘局部区域。示例取两者的交集,并保留 Graphics 原有的裁剪限制,避免画到其他单元格。

标题底线也应使用当前单元格的 Left 和 Right。原代码从 x = 0 开始,两个端点的纵坐标也不同,画出的是一条略微倾斜、跨越其他区域的线。

如果只需要统一的竖向网格线,可以考虑使用内置边框:

dgv.CellBorderStyle = DataGridViewCellBorderStyle.SingleVertical;
dgv.GridColor = Color.White;

这会把数据单元格边框改为竖向单线,适合不需要横向边框的情况;列标题的自定义绘制仍可单独保留。

如何确认修改有效

重点检查拖动水平滚动条、首个可见列只显示一部分、调整列宽以及选中单元格后的显示情况。预期每个可见单元格在完成默认绘制后,都会在自身右边缘补画白线。

如果问题仍然存在,再检查是否有其他绘制事件处理程序或 DataGridView 子类覆盖了绘制逻辑。输入中没有提供这些信息,上述示例也未在原项目中运行验证。

备注:内容仅供参考。