4

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

因此,如果我的用户控件是 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();
}
4

1 回答 1

10

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

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

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

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

当您的可视对象的状态发生变化时(例如,鼠标或键盘事件或对数据变化做出反应),通常只需调用 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;
}
于 2012-11-19T19:18:45.483 回答