WinAPI的双缓冲技术

6
好的,我的应用程序中有许多WinAPI和一些自定义控件。通常情况下,它们会在动画、状态更改等方面静默地重绘自己,一切都很正常。
但是,我有一个名为fix()的Window类方法。每当整个窗口需要更新时,就会调用该方法。它会调整控件的大小并使窗口失效。
当这种情况发生时,首先绘制背景,然后是选项卡控件,然后是其他所有控件。这会导致非常恼人的闪烁,特别是在调整窗口大小时(因为不断调用fix())。
我尝试过的解决方法:
- WS_EX_COMPOSITED。这只是双缓冲单个控件。这是一个进步,但闪烁仍然不可避免。 - 关闭背景绘制。几乎无法解决问题,实际上还会使问题变得更糟。
所以:我需要一种技术/方法/任何东西来允许我完全双缓冲窗口。我想处理WM_PAINT消息可能是一个解决办法,但我不知道从哪里开始。我非常担心这甚至不可能...
请帮忙,这是一个关键问题。当这个愚蠢的小问题得到解决时,我会非常宽慰。

也许可以查看这个页面?http://www.gamedev.net/community/forums/topic.asp?topic_id=411559 - Prof. Falken
5个回答

6
在这个时候,人们意识到微软对本地开发者的漠视程度。一个人甚至会开始怀疑微软故意破坏本地绘图,以迫使本地开发者转向WPF。
首先,考虑WS_EX_COMPOSITED。WS_EX_COMPOSITED似乎是芥末——它表示强制子控件按从下到上的顺序进行绘制,并且基本上WM_PAINT消息会被批处理处理。它说它是在Windows 2000(5.0)中添加的,而在几行之后,它又说它不适用于启用桌面组合的情况。也就是说,自Windows Vista(6.0)起就不再起作用,除非关闭了aero glass,但谁会这么做呢?
然后,有两种可能的“黑科技”可以尝试实现无闪烁绘图:
首先,您需要最小化过度绘制的量。必须使用WS_EX_CLIPCHILDREN | WS_EX_CLIPSIBLINGS来确保窗口的任何特定区域只绘制一次。BeginDeferWindowPos也是必要的,以批处理调整大小操作,以确保瞬态状态(其中一个窗口重叠另一个窗口)不会发生(即当窗口A已调整大小但窗口B没有调整大小时)。
当然,当您尝试绘制皮肤对话框、使用分组框或选项卡控件或任何其他限制时,WS_EX_CLIPSIBLINGS就不合适了。
WM_SETREDRAW是一个神奇的消息。没有API可以访问此功能:DefWindowProc直接处理WM_SETREDRAW,以基本上将窗口标记为隐藏状态。发送WM_SETREDRAW,FALSE后,使用父窗口句柄(及其所有子项)调用GetDC / GetDCEx / GetWindowDC等将返回一个不会在屏幕上绘制的DC。这给了你一个机会去做所有种类的事情来处理子窗口,当你完成后发送WM_SETREDRAW,TRUE(然后手动重绘窗口)。所有子窗口都会——当然——在它们自己的时间内绘制,并且在父窗口完成擦除背景之后,因此WM_SETREDRAW不是什么万能药。
在破坏WS_EX_COMPOSITED之后,在.NET的WinForms和WPF中从头开始重新编写控件,以不使用本地控件,因此他们可以在那里嵌入缓冲绘图和alpha支持。

在.NET的WinForms和WPF中,重新编写了控件,从头开始不使用本地控件,以便可以在其中嵌入缓冲绘制。哦,我的天啊,这种可能性甚至没有跨越我的脑海!我想那大概是在.NET 2.0时代吧。我一直想知道为什么特别是那个标志如此破碎,现在我的头脑中突然有很多事情变得清晰了。 - dialer

1

嗯。在人们匆忙将此主题关闭为重复之前,我最好提到您的问题不是双缓冲本身,而是重新定位控件时出现闪烁,手动双缓冲只是几种解决方案中的一种。

可能会有例如BeginDeferWindowPos和其他方法可以修复闪烁问题。

免责声明:我曾经几乎了解Win16 API的所有细节,但已经有一些年没有进行API级别的编程了。

祝好运。

– Alf


@Alexander,win32只是win16的不那么邪恶的孪生兄弟。 - Prof. Falken

0

没有代码,我很难想象实际情况是什么... 但你可以尝试处理WM_ERASEBKGND,每当你收到它们时返回TRUE以跳过擦除。


我尝试过这个。它只会造成一大堆问题。如果你不擦除背景,你知道会发生什么吗? - Alexander Rafferty
背景显然没有被“擦除”:>。如果你的绘画覆盖了整个区域,那么它是否被擦除并不重要。 - YeenFei

0
处理窗口的 WM_ERASEBKGND,在实现中排除每个子控件的矩形,然后适当填充剩余的背景,并告诉框架你已经自己绘制了背景,通过返回 true
对于所有其他子控件也要做同样的事情,这些子控件又包含其他控件,例如在您的情况下是选项卡控件。
请参见 此处 以查看使用 MFC 的示例。

0
在 .net 中,有一个名为 ControlStyle.AllPaintingInWmPaint 的属性,应该在每个容器窗口上设置(包括主窗口和选项卡)。我找不到 WinAPI 的等效方法。但是这个属性的作用是将背景的绘制从 WM_ERASEBKGND 移动到 WM_PAINT。同时,它还会从父控件中减去子控件的区域以避免闪烁。可能需要手动执行此操作。
我很惊讶 WS_EX_COMPOSITED 没有起到帮助作用。如果您在顶层窗口上设置了它,并且不对子控件调用 RedrawWindow,那么它应该可以工作。
另外,请务必测试启用和禁用 DWM/Aero 的情况。您可能会得到不同的结果。

我之前的回答中放了一段C#代码示例,你可以尝试修改它来展示你所看到的效果。https://dev59.com/xVDTa4cB1Zd3GeqPJ4rP#3735328 - user180326
@Alexander:这就是你需要做的。你还需要在WM_ERASEBKGND上返回true。从绘制中排除子窗口矩形也可能是必要的(也许添加WS_CLIPCHILDREN可以解决问题)。 - user180326
你是在告诉我窗口双缓冲是不可能的吗? - Alexander Rafferty
@Alexander:通常每个窗口都会自己绘制。每个控件(包括按钮)都有自己的窗口句柄。如果您想要双缓冲,可以单独为每个控件进行双缓冲(而不是整个表单)。但是,当您使用WS_EX_COMPOSITED时,具有该样式的窗口和所有子控件将在一个过程中离屏渲染,然后作为单个矩形复制。缺点是它速度较慢,需要XP或更高版本,并且有时会引入伪影(特别是在使用Aero/DWM调整大小时出现黑色区域)。 - user180326
这就是我所需要的,一个完全双缓冲窗口,但 WS_EX_COMPOSITED 不起作用! - Alexander Rafferty
@Alexander:请不要向我抱怨。你应该知道,当你用C++编写win32程序时,需要进行一些手动操作。 - user180326

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