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 子类覆盖了绘制逻辑。输入中没有提供这些信息,上述示例也未在原项目中运行验证。
备注:内容仅供参考。
