Winforms中自定义控件绘制的最佳实践是什么?

7
通常情况下,当我重写OnPaint方法时,我会在其中创建笔刷等对象,然后在其内部进行处理并释放它们。
我也在某个地方读到过,可以将这些笔刷等对象作为静态成员变量创建一次,然后在窗体关闭时进行一次释放。
这是更好的做法吗?
是否有更好的方式?
我可以假设由于OnPaint方法被调用了1000多次,与仅创建一次相比,这将为GC带来很多工作。
5个回答

9
如果画笔和笔刷不会改变,最好只创建一次并重复使用。然而,请注意,如果您的控件可能在多个线程上使用(这是非常不可能的),则应将它们设置为 ThreadStatic (并在每个线程上首次使用时进行初始化)或将它们设置为实例成员,并在控件的 Dispose 覆盖中进行处理;否则,您将遇到无法再现的GDI +错误,因为GDI +对象不能同时在多个线程上使用。同样适用于图像。
如果它们发生了更改(例如,如果您使用依赖于控件大小的渐变笔刷),您仍然可能希望将它们存储在实例字段中,并在控件的大小(或其他内容)更改时重新创建它们。
顺便提一下,如果您使用普通颜色,则可以使用静态BrushesPens类,其中包含所有.Net内置颜色的静态笔刷和笔,以及SystemBrushesSystemPens系统颜色。

谢谢,再问一个问题:您什么时候以及如何调用控件的Dispose方法?我以前从未调用过该控件上的此方法。 - Joan Venge
1
不需要手动调用它;.Net 会在父控件被释放时自动调用它(当您释放包含它的窗体时)。如果它在您的主窗体上,它将在 Application.Run 调用结束时被释放。 - SLaks
谢谢Slaks。还有一件事 :) 由于我通常只使用一个表单,如果我创建另一个不是主表单的表单,那么在它关闭后我必须将其处理掉吗? - Joan Venge
3
只有通过 .Show 或 Application.Run(或等效方式)显示的窗体在关闭时才会自动释放。使用 .ShowDialog 显示的窗体在关闭时不会被释放。 - homeInAStar
@homeInAStar:是的,你说得对;我没有仔细阅读这个方法。在调用ShowDialog时,最好将代码放在using语句中。 - SLaks

2

上个月我阅读了一篇有趣的文章,其中提到将所有绘画都放在单独的BufferedGraphics对象上,并使on_paint方法直接从该对象复制到控件的graphics对象。

这种方法可以使on-paint更快,并且只有在重要变化发生时(例如线条移动或文本更改)才更新您的BufferedGraphics。


1
嗯,不是很出色。它假设一些坏事情,比如绘制整个控件而不是剪切矩形。大多数情况下,这不会是最快的方法。此外,他手动创建了一个双缓冲区,而不是使用系统提供的ControlStyles.DoubleBuffer,显然没有理由。将代码从OnPaint处理程序中取出可能是好的,但是这篇文章还有更多“额外”的功能,这些功能并不是很好。 - David Suarez

1
我会将画笔和笔刷作为自定义控件的成员,然后在处理控件时一并处理它们。这样每次调用OnPaint时都可以重复使用相同的画笔/笔刷。
我不会将它们声明为“静态”,因为您无法知道何时可以处理对象。但正如SLaks所提到的,如果同时存在许多控件实例,则将画笔和笔刷创建为静态对象可能是一个好主意,这样您就只需为应用程序的生命周期创建一个对象实例。

如果将有许多控件实例,则将它们设为静态是一个非常好的主意。拥有几个画笔并没有什么问题。 - SLaks
1
这是一个很好的观点,如果有很多此控件的实例,将画笔和笔刷设为静态会是一个不错的选择。 - Meta-Knight

1

这取决于你要绘制什么。如果你要绘制的东西只有在用户交互时才会重新绘制,那么你可以不用担心性能问题,需要时即时创建所有图形对象。

确保你在图形中Dispose()了所有需要的东西。笔、画刷、区域、字体。它们都是GDI对象,并通过GDI句柄与系统相连。

如果你需要动画或者需要随时间变化而改变的图形,预先准备好所有的图形对象,并尽可能地重复使用它们。这里可以应用的规则是,浪费内存总比每帧动画绘制花费毫秒要好。

最后,至少对于这篇文章来说 - 不要忘记使用双缓冲,无论是在.net控件系统中自动实现还是自己实现回退位图样式。

享受GDI的乐趣吧 :)


0
最佳实践是使用系统笔和画笔,因为它们经过优化以消耗最少的资源。

只有在使用静态内置颜色时才是这样。 - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接