我有一个用户控件,其中包含许多自己绘制的对象的完全自定义绘制的图形(从 OnPaint 调用),背景是一个大的位图。我内置了缩放和平移功能,并且 Canvas 上绘制的对象的所有坐标都是位图坐标。

因此,如果我的用户控件宽度为 1000 像素,位图宽度为 1500 像素,并且缩放比例为 200%,那么在任何给定时间我只会看到位图宽度的 1/3。如果您滚动到最左侧,则一个对象在位图上的点 100,100 处有一个矩形,它将出现在屏幕上的点 200,200。

基本上我需要做的是创建一种仅重绘需要重绘的有效方式。例如,如果我移动一个对象,我可以将该对象的旧剪辑矩形添加到一个区域,并将该对象的新剪辑矩形合并到同一区域,然后调用 Invalidate(region) 重绘这两个区域。

然而,这样做意味着我必须不断将对象位图坐标转换为屏幕坐标,然后再将它们提供给 Invalidate。当其他窗口使我的窗口无效时,我必须始终假设 PaintEventArgs 中的 ClipRectangle 在屏幕坐标中。

有没有办法可以利用 Region.Transform 和 Region.Translate 功能,这样我就不需要从位图转换为屏幕坐标?以一种不会干扰在屏幕坐标中接收 PaintEventArgs 的方式?我应该使用多个区域还是有更好的方法来做到这一切?

我现在正在做的示例代码:

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

SelectedItem.UpdateEndPoint(endPoint);

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

this.Invalidate(invalidateRegion);

并且在 OnPaint()...
protected override void OnPaint(PaintEventArgs e)
{
    invalidateRegion.Union(e.ClipRectangle);

    e.Graphics.SetClip(invalidateRegion, CombineMode.Union);
    e.Graphics.Clear(SystemColors.AppWorkspace);

    e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y);

    DrawCanvas(e.Graphics, _ratio);

    e.Graphics.ResetTransform();

    e.Graphics.ResetClip();

    invalidateRegion.MakeEmpty();
}

最佳答案

由于很多人都在查看这个问题,我将继续并根据我目前的知识来回答它。

随 PaintEventArgs 提供的 Graphics 类始终由失效请求硬剪裁。这通常由操作系统完成,但也可以由您的代码完成。

您无法重置此剪辑或摆脱这些剪辑边界,但您不需要这样做。绘画时,您通常不应该关心它是如何被剪裁的,除非您迫切需要最大限度地提高性能。

图形类使用一堆容器来应用剪辑和转换。您可以使用 Graphics.BeginContainer 和 Graphics.EndContainer 自己扩展此堆栈。每次启动容器时,您对 Transform 或 Clip 所做的任何更改都是临时的,它们会在任何先前在 BeginContainer 之前配置的 Transform 或 Clip 之后应用。所以本质上,当你得到一个 OnPaint 事件时,它已经被剪裁了,你在一个新的容器中,所以你看不到剪辑(你的剪辑区域或 ClipRect 将显示为无限),你不能摆脱那些剪辑边界。

当您的可视对象的状态发生变化时(例如,鼠标或键盘事件或对数据变化使用react),通常只需调用 Invalidate() 即可,它会重新绘制整个控件。 Windows 将在 CPU 使用率低的时候调用 OnPaint。每次调用 Invalidate() 通常并不总是对应一个 OnPaint 事件。 Invalidate 可以在下一次绘制之前多次调用。因此,如果您的数据模型中的 10 个属性同时更改,您可以安全地在每次属性更改时调用 Invalidate 10 次,并且您可能只会触发一个 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;
}

关于c# - OnPaint、Invalidate、Clipping 和 Region 的最佳实践,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7507602/

10-09 01:46