关于OnPaint、Invalidate、Clipping和Regions的最佳实践
2026-01-06 10:11:40
由于很多人正在查看这个问题,我将尽我目前所知来回答它。
PaintEventArgs提供的Graphics类始终被无效请求进行硬裁剪。通常这是由操作系统完成的,但也可以由您的代码完成。
您不能重置此裁剪或逃脱这些裁剪边界,但您不应该需要这样做。在绘制时,通常不应该关心如何进行裁剪,除非您迫切需要最大化性能。
Graphics类使用一堆容器来应用裁剪和变换。您可以使用Graphics.BeginContainer和Graphics.EndContainer自行扩展此堆栈。每次开始容器时,您对Transform或Clip所做的任何更改都是临时的,并且它们会在之前配置的任何先前的Transform或Clip之后应用。因此,当您获得OnPaint事件时,它已经被裁剪,并且您处于新容器中,因此您无法看到裁剪(您的Clip区域或ClipRect将显示为无限),也无法突破这些裁剪边界。
当视觉对象的状态发生更改时(例如在鼠标或键盘事件或响应数据更改时),通常直接调用Invalidate()即可对整个控件进行重绘。Windows会在CPU使用率较低时调用OnPaint。每次调用Invalidate()通常不会始终对应一个OnPaint事件。在下一次绘图之前,可能会多次调用Invalidate。因此,如果您的数据模型中同时更改了10个属性,则可以在每个属性更改上安全地调用10次Invalidate,并且您可能只会触发单个OnPaint事件。
我注意到您在使用Update()和Refresh()时需要小心。它们将立即强制同步OnPaint。它们对于单线程操作(例如更新进度条)很有用,但是在错误的时间使用它们可能会导致过度和不必要的重绘。
如果您想使用剪辑矩形来提高重绘场景的性能,您无需自己跟踪聚合的剪辑区域。Windows会为您完成此操作。只需使矩形或需要无效化的区域失效,然后按常规方式进行绘制即可。例如,如果您正在绘制的对象被移动,则每次您要使其旧边界和新边界失效,以便在其原始位置重新绘制背景并在其新位置上进行绘制。您还必须考虑笔画大小等因素。
正如Hans Passant所提到的,始终使用32bppPArgb作为高分辨率图像的位图格式。下面是一个加载“高性能”图像的代码片段:
public static Bitmap GetHighPerformanceBitmap(Image original)
{
Bitmap bitmap;
bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb);
bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel);
}
return bitmap;
}