当用户调整对话框大小时,如何强制窗口不重绘对话框中的任何内容?
当用户抓住可调整大小的窗口的一角,然后移动它时,窗口首先移动窗口的内容,然后向正在调整大小的窗口发出 WM_SIZE。
因此,在一个对话框中,我想要控制各种子控件的移动,并且想要消除闪烁,用户首先看到 Windows 操作系统认为窗口的样子(因为,AFAICT,操作系统使用 bitblt 方法来移动在发送 WM_SIZE 之前,窗口内的事物) - 只有然后我的对话框才能处理移动其子控件,或调整它们的大小等,之后它必须强制重新绘制事物,这现在会导致闪烁(至少)。
我的主要问题是:有没有办法强制窗口不做这种愚蠢的 bitblt 事情?如果窗口的控件随着窗口大小的调整而移动,那么它肯定是错误的,或者当它们的父级调整大小时,它们也会调整自己的大小。不管怎样,让操作系统进行预绘制只会搞砸工作。
我一度认为这可能与 CS_HREDRAW 和 CSVREDRAW 类标志有关。然而,现实情况是,我不希望操作系统要求我擦除窗口 - 我只想自己重新绘制,而不需要操作系统首先更改窗口的内容(即我希望显示是原来的样子)在用户开始调整大小之前 - 没有来自操作系统的任何位块传输)。而且我也不希望操作系统告诉每个控件它需要重新绘制(除非它恰好是实际上因调整大小而被遮盖或显示的控件)。
我真正想要的是:
- 移动和调整子控件的大小<在屏幕上更新任何内容之前,
- 子控件,以便它们在新的大小和位置上不出现任何瑕疵。
- 完全绘制所有已移动或调整大小的 。
2 和 3 可以颠倒过来
注意:当我将 DeferSetWindowPos() 与标记为 WS_CLIPCHILDREN 的对话框资源结合使用时,
步骤 我可以对内存 DC 执行上述操作,然后只在 WM_SIZE 处理程序的末尾执行一个 bitblt
我已经使用了一段时间了,但我无法逃脱两件事:
我仍然无法逃脱。禁止 Windows 执行“预测性 bitblt”答案:请参阅下面的覆盖 WM_NCCALCSIZE 来禁用此行为的解决方案。
我看不出如何构建一个对话框,其中的子控件绘制到双缓冲区。 答案:请参阅下面 John 的答案(标记为答案),了解如何要求 Windows 操作系统对对话框进行双缓冲(注意:根据文档,这不允许在绘制操作之间进行任何 GetDC() 操作)。
我的最终解决方案(感谢所有做出贡献的人,尤其是 John K.):
经过大量的汗水和泪水,我发现以下技术无论在 Aero 和 XP 中还是在禁用 Aero 的情况下都可以完美地工作。轻弹是不存在的(1)。
- 挂钩对话框过程。
- 覆盖 WM_NCCALCSIZE 以强制 Windows 验证整个客户区,而不是 bitblt 任何内容。
- 覆盖 WM_SIZE 来完成所有的移动和操作。使用 BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos 调整所有可见窗口的大小。
- 确保对话框窗口具有 WS_CLIPCHILDREN 样式。
- 不要使用 CS_HREDRAW|CS_VREDRAW(对话框不使用,所以通常不是问题)。
布局代码由您决定 - 在布局管理器的 CodeGuru 或 CodeProject 上很容易找到示例,或者推出您自己的示例。
以下是一些代码摘录,应该可以帮助您大部分了解:
LRESULT ResizeManager::WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_ENTERSIZEMOVE:
m_bResizeOrMove = true;
break;
case WM_NCCALCSIZE:
// The WM_NCCALCSIZE idea was given to me by John Knoeller:
// see: http://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resiz
//
// The default implementation is to simply return zero (0).
//
// The MSDN docs indicate that this causes Windows to automatically move all of the child controls to follow the client's origin
// and experience shows that it bitblts the window's contents before we get a WM_SIZE.
// Hence, our child controls have been moved, everything has been painted at its new position, then we get a WM_SIZE.
//
// Instead, we calculate the correct client rect for our new size or position, and simply tell windows to preserve this (don't repaint it)
// and then we execute a new layout of our child controls during the WM_SIZE handler, using DeferWindowPos to ensure that everything
// is moved, sized, and drawn in one go, minimizing any potential flicker (it has to be drawn once, over the top at its new layout, at a minimum).
//
// It is important to note that we must move all controls. We short-circuit the normal Windows logic that moves our child controls for us.
//
// Other notes:
// Simply zeroing out the source and destination client rectangles (rgrc[1] and rgrc[2]) simply causes Windows
// to invalidate the entire client area, exacerbating the flicker problem.
//
// If we return anything but zero (0), we absolutely must have set up rgrc[0] to be the correct client rect for the new size / location
// otherwise Windows sees our client rect as being equal to our proposed window rect, and from that point forward we're missing our non-client frame
// only override this if we're handling a resize or move (I am currently unaware of how to distinguish between them)
// though it may be adequate to test for wparam != 0, as we are
if (bool bCalcValidRects = wparam && m_bResizeOrMove)
{
NCCALCSIZE_PARAMS * nccs_params = (NCCALCSIZE_PARAMS *)lparam;
// ask the base implementation to compute the client coordinates from the window coordinates (destination rect)
m_ResizeHook.BaseProc(hwnd, msg, FALSE, (LPARAM)&nccs_params->rgrc[0]);
// make the source & target the same (don't bitblt anything)
// NOTE: we need the target to be the entire new client rectangle, because we want windows to perceive it as being valid (not in need of painting)
nccs_params->rgrc[1] = nccs_params->rgrc[2];
// we need to ensure that we tell windows to preserve the client area we specified
// if I read the docs correctly, then no bitblt should occur (at the very least, its a benign bitblt since it is from/to the same place)
return WVR_ALIGNLEFT|WVR_ALIGNTOP;
}
break;
case WM_SIZE:
ASSERT(m_bResizeOrMove);
Resize(hwnd, LOWORD(lparam), HIWORD(lparam));
break;
case WM_EXITSIZEMOVE:
m_bResizeOrMove = false;
break;
}
return m_ResizeHook.BaseProc(hwnd, msg, wparam, lparam);
}
调整大小实际上是由 Resize() 成员完成的,如下所示:
// execute the resizing of all controls
void ResizeManager::Resize(HWND hwnd, long cx, long cy)
{
// defer the moves & resizes for all visible controls
HDWP hdwp = BeginDeferWindowPos(m_resizables.size());
ASSERT(hdwp);
// reposition everything without doing any drawing!
for (ResizeAgentVector::const_iterator it = m_resizables.begin(), end = m_resizables.end(); it != end; ++it)
VERIFY(hdwp == it->Reposition(hdwp, cx, cy));
// now, do all of the moves & resizes at once
VERIFY(EndDeferWindowPos(hdwp));
}
也许最后的棘手部分可以在 ResizeAgent 的 Reposition() 处理程序中看到:
HDWP ResizeManager::ResizeAgent::Reposition(HDWP hdwp, long cx, long cy) const
{
// can't very well move things that no longer exist
if (!IsWindow(hwndControl))
return hdwp;
// calculate our new rect
const long left = IsFloatLeft() ? cx - offset.left : offset.left;
const long right = IsFloatRight() ? cx - offset.right : offset.right;
const long top = IsFloatTop() ? cy - offset.top : offset.top;
const long bottom = IsFloatBottom() ? cy - offset.bottom : offset.bottom;
// compute height & width
const long width = right - left;
const long height = bottom - top;
// we can defer it only if it is visible
if (IsWindowVisible(hwndControl))
return ::DeferWindowPos(hdwp, hwndControl, NULL, left, top, width, height, SWP_NOZORDER|SWP_NOACTIVATE);
// do it immediately for an invisible window
MoveWindow(hwndControl, left, top, width, height, FALSE);
// indicate that the defer operation should still be valid
return hdwp;
}
“棘手”我们避免尝试弄乱任何已被破坏的窗口,并且我们不会尝试针对不可见的窗口推迟 SetWindowPos(因为这被记录为“将失败”。
我已经在一个隐藏一些控件并使用相当复杂的布局并取得了巨大成功的真实项目,即使没有 Aero,即使您使用对话框窗口的左上角调整大小(大多数可调整大小的窗口都会显示),闪烁也为零 (1)。 问题 - IE、FireFox 等)。
当你抓住这个句柄时,
大多数闪烁和 ) 请注意,不可能避免在过去的任何东西之上进行抽签。对于对话框中未更改的每个部分,用户看不到任何内容(没有任何闪烁)。但是,如果事情发生了变化,用户就会看到变化 - 这是无法避免的,并且是 100% 的解决方案。
When the user grabs a corner of a resizable window, and then moves it, windows first moves the contents of the window around, then issues a WM_SIZE to the window being resized.
Thus, in a dialog where I want to control the movement of various child controls, and I want to eliminate flickering, the user first sees what windows OS thinks the window will look like (because, AFAICT, the OS uses a bitblt approach to moving things around inside the window before sending the WM_SIZE) - and only then does my dialog get to handle moving its child controls around, or resize them, etc., after which it must force things to repaint, which now causes flicker (at the very least).
My main question is: Is there a way to force windows NOT to do this stupid bitblt thing? Its definitely going to be wrong in the case of a window with controls that move as the window is resized, or that resize themselves as their parent is resized. Either way, having the OS do a pre-paint just screws the works.
I thought for a time that it might be related to CS_HREDRAW and CSVREDRAW class flags. However, the reality is that I don't want the OS to ask me to erase the window - I just want to do the repainting myself without the OS first changing the contents of my window (i.e. I want the display to be what it was before the user started resizing - without any bitblit'ing from the OS). And I don't want the OS to tell every control that it needs to be redrawn either (unless it happened to be one that was in fact obscured or revealed by the resize.
What I really want:
- To move & resize child controls before anything gets updated onscreen.
- Draw all of the moved or resized child controls completely so that they appear without artifacts at their new size & location.
- Draw the spaces inbetween the child controls without impacting the child controls themselves.
NOTE: Steps 2 and 3 could be reversed.
The above three things appear to happen correctly when I use DeferSetWindowPos() in combination with the dialog resource marked as WS_CLIPCHILDREN.
I'd get an additional small benefit if I could do the above to a memory DC, and then only do a single bitblt at the end of the WM_SIZE handler.
I have played with this for a while now, and I cannot escape two things:
I still am unable to suppress Windows from doing a 'predictive bitblt'. Answer: See below for a solution that overrides WM_NCCALCSIZE to disable this behavior.
I cannot see how one can build a dialog where its child controls draw to a double buffer. Answer: See John's answer (marked as answer) below for how to ask Windows OS to double buffer your dialog (note: this disallows any GetDC() in-between paint operations, according to the docs).
My Final Solution (Thank you everyone who contributed, esp. John K.):
After much sweat and tears, I have found that the following technique works flawlessly, both in Aero and in XP or with Aero disabled. Flicking is non-existent(1).
- Hook the dialog proc.
- Override WM_NCCALCSIZE to force Windows to validate the entire client area, and not bitblt anything.
- Override WM_SIZE to do all of your moves & resizes using BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos for all visible windows.
- Ensure that the dialog window has the WS_CLIPCHILDREN style.
- Do NOT use CS_HREDRAW|CS_VREDRAW (dialogs don't, so generally not an issue).
The layout code is up to you - its easy enough to find examples on CodeGuru or CodeProject of layout managers, or to roll your own.
Here are some code excerpts that should get you most of the way:
LRESULT ResizeManager::WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_ENTERSIZEMOVE:
m_bResizeOrMove = true;
break;
case WM_NCCALCSIZE:
// The WM_NCCALCSIZE idea was given to me by John Knoeller:
// see: http://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resiz
//
// The default implementation is to simply return zero (0).
//
// The MSDN docs indicate that this causes Windows to automatically move all of the child controls to follow the client's origin
// and experience shows that it bitblts the window's contents before we get a WM_SIZE.
// Hence, our child controls have been moved, everything has been painted at its new position, then we get a WM_SIZE.
//
// Instead, we calculate the correct client rect for our new size or position, and simply tell windows to preserve this (don't repaint it)
// and then we execute a new layout of our child controls during the WM_SIZE handler, using DeferWindowPos to ensure that everything
// is moved, sized, and drawn in one go, minimizing any potential flicker (it has to be drawn once, over the top at its new layout, at a minimum).
//
// It is important to note that we must move all controls. We short-circuit the normal Windows logic that moves our child controls for us.
//
// Other notes:
// Simply zeroing out the source and destination client rectangles (rgrc[1] and rgrc[2]) simply causes Windows
// to invalidate the entire client area, exacerbating the flicker problem.
//
// If we return anything but zero (0), we absolutely must have set up rgrc[0] to be the correct client rect for the new size / location
// otherwise Windows sees our client rect as being equal to our proposed window rect, and from that point forward we're missing our non-client frame
// only override this if we're handling a resize or move (I am currently unaware of how to distinguish between them)
// though it may be adequate to test for wparam != 0, as we are
if (bool bCalcValidRects = wparam && m_bResizeOrMove)
{
NCCALCSIZE_PARAMS * nccs_params = (NCCALCSIZE_PARAMS *)lparam;
// ask the base implementation to compute the client coordinates from the window coordinates (destination rect)
m_ResizeHook.BaseProc(hwnd, msg, FALSE, (LPARAM)&nccs_params->rgrc[0]);
// make the source & target the same (don't bitblt anything)
// NOTE: we need the target to be the entire new client rectangle, because we want windows to perceive it as being valid (not in need of painting)
nccs_params->rgrc[1] = nccs_params->rgrc[2];
// we need to ensure that we tell windows to preserve the client area we specified
// if I read the docs correctly, then no bitblt should occur (at the very least, its a benign bitblt since it is from/to the same place)
return WVR_ALIGNLEFT|WVR_ALIGNTOP;
}
break;
case WM_SIZE:
ASSERT(m_bResizeOrMove);
Resize(hwnd, LOWORD(lparam), HIWORD(lparam));
break;
case WM_EXITSIZEMOVE:
m_bResizeOrMove = false;
break;
}
return m_ResizeHook.BaseProc(hwnd, msg, wparam, lparam);
}
The resizing is really done by the Resize() member, like so:
// execute the resizing of all controls
void ResizeManager::Resize(HWND hwnd, long cx, long cy)
{
// defer the moves & resizes for all visible controls
HDWP hdwp = BeginDeferWindowPos(m_resizables.size());
ASSERT(hdwp);
// reposition everything without doing any drawing!
for (ResizeAgentVector::const_iterator it = m_resizables.begin(), end = m_resizables.end(); it != end; ++it)
VERIFY(hdwp == it->Reposition(hdwp, cx, cy));
// now, do all of the moves & resizes at once
VERIFY(EndDeferWindowPos(hdwp));
}
And perhaps the final tricky bit can be seen in the ResizeAgent's Reposition() handler:
HDWP ResizeManager::ResizeAgent::Reposition(HDWP hdwp, long cx, long cy) const
{
// can't very well move things that no longer exist
if (!IsWindow(hwndControl))
return hdwp;
// calculate our new rect
const long left = IsFloatLeft() ? cx - offset.left : offset.left;
const long right = IsFloatRight() ? cx - offset.right : offset.right;
const long top = IsFloatTop() ? cy - offset.top : offset.top;
const long bottom = IsFloatBottom() ? cy - offset.bottom : offset.bottom;
// compute height & width
const long width = right - left;
const long height = bottom - top;
// we can defer it only if it is visible
if (IsWindowVisible(hwndControl))
return ::DeferWindowPos(hdwp, hwndControl, NULL, left, top, width, height, SWP_NOZORDER|SWP_NOACTIVATE);
// do it immediately for an invisible window
MoveWindow(hwndControl, left, top, width, height, FALSE);
// indicate that the defer operation should still be valid
return hdwp;
}
The 'tricky' being that we avoid trying to mess with any windows that have been destroyed, and we don't try to defer a SetWindowPos against a window that is not visible (as this is documented as "will fail".
I've tested the above in a real project that hides some controls, and makes use of fairly complex layouts with excellent success. There is zero flickering(1) even without Aero, even when you resize using the upper left corner of the dialog window (most resizable windows will show the most flickering and problems when you grab that handle - IE, FireFox, etc.).
If there is interest enough, I could be persuaded to edit my findings with a real example implementation for CodeProject.com or somewhere similar. Message me.
(1) Please note that it is impossible to avoid one draw over the top of whatever used to be there. For every part of the dialog that has not changed, the user can see nothing (no flicker whatsoever). But where things have changed, there is a change visible to the user - this is impossible to avoid, and is a 100% solution.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
您无法在调整大小期间阻止绘画,但您可以(小心地)阻止重新绘画,这就是闪烁的根源。首先,bitblt。
有两种方法可以阻止 bitblt 的发生。
如果您拥有顶级窗口的类,则只需使用
CS_HREDRAW | 注册它即可。 CS_VREDRAW
样式。这将导致调整窗口大小以使整个客户区域无效,而不是尝试猜测哪些位不会更改并进行位转移。如果您不拥有该类,但有能力控制消息处理(对于大多数对话框都是如此)。
WM_NCCALCSIZE
的默认处理是处理类样式CS_HREDRAW
和CS_VREDRAW
的位置,默认行为是返回WVR_HREDRAW |当类具有
。CS_HREDRAW | WVR_VREDRAW
时,不会处理WM_NCCALCSIZE
CS_VREDRAW因此,如果您可以拦截
WM_NCCALCSIZE
,则可以在调用DefWindowProc
之后强制返回这些值,以进行其他正常处理。您可以收听
WM_ENTERSIZEMOVE
和WM_EXITSIZEMOVE
来了解窗口大小调整何时开始和停止,并使用它来暂时禁用或修改绘图和/或布局代码的工作方式以尽量减少闪烁。您究竟想要做什么来修改此代码取决于您的普通代码通常在WM_SIZE
WM_PAINT
和WM_ERASEBKGND
中执行的操作。当您绘制对话框的背景时,您不需要在任何子窗口后面绘制。确保对话框具有 WS_CLIPCHILDREN 可以解决此问题,因此您已经处理了此问题。
当您移动子窗口时,请确保使用
BeginDeferWindowPos
/EndDefwindowPos
以便所有重画同时发生。否则,当每个窗口在每次SetWindowPos
调用时重新绘制其非客户区域时,您将会看到一堆闪烁。You can't prevent painting during resizing, but you can (with care) prevent repainting which is where flicker comes from. first, the bitblt.
There a two ways to stop the bitblt thing.
If you own the class of the top level window, then just register it with the
CS_HREDRAW | CS_VREDRAW
styles. This will cause a resize of your window to invalidate the entire client area, rather than trying to guess which bits are not going to change and bitblting.If you don't own the class, but do have the ability to control message handling (true for most dialog boxes). The default processing of
WM_NCCALCSIZE
is where the class stylesCS_HREDRAW
andCS_VREDRAW
are handled, The default behavior is to returnWVR_HREDRAW | WVR_VREDRAW
from processingWM_NCCALCSIZE
when the class hasCS_HREDRAW | CS_VREDRAW
.So if you can intercept
WM_NCCALCSIZE
, you can force the return of these values after callingDefWindowProc
to do the other normal processing.You can listen to
WM_ENTERSIZEMOVE
andWM_EXITSIZEMOVE
to know when resizing of your window starts and stops, and use that to temporarily disable or modify the way your drawing and/or layout code works to minimize the flashing. What exactly you want to do to modify this code will depend on what your normal code normally does inWM_SIZE
WM_PAINT
andWM_ERASEBKGND
.When you paint the background of your dialog box, you need to not paint behind any of the child windows. making sure that the dialog has
WS_CLIPCHILDREN
solves this, so you have this handled already.When you do move the child windows, Make sure that you use
BeginDeferWindowPos
/EndDefwindowPos
so that all of the repainting happens at once. Otherwise you will get a bunch of flashing as each window redraws their nonclient area on eachSetWindowPos
call.如果我正确理解了这个问题,这正是雷蒙德今天解决的问题。
If I understood the question properly, it's exactly the question Raymond addressed today.
这是 2018 年的更新,因为我刚刚经历了和你一样的挑战。
您问题中的“最终解决方案”以及相关答案提到了
WM_NCCALCSIZE
和CS_HREDRAW|CS_VREDRAW
的技巧,有助于防止 Windows XP/Vista/7 执行以下操作:BitBlt
在调整大小期间干扰您的客户区。提及类似的技巧甚至可能很有用:您可以拦截WM_WINDOWPOSCHANGING
(首先将其传递到DefWindowProc
)并设置WINDOWPOS.flags |= SWP_NOCOPYBITS
>,这会禁用 Windows 在调整窗口大小期间对SetWindowPos()
进行的内部调用中的BitBlt
。这与跳过BitBlt
具有相同的最终效果。有些人提到您的
WM_NCCALCSIZE
技巧在 Windows 10 中不再有效。我认为这可能是因为您编写的代码在应该返回WVR_ALIGNLEFT|WVR_ALIGNTOP
时返回了WVR_ALIGNLEFT|WVR_ALIGNTOP
>WVR_VALIDRECTS 以便使用您构建的两个矩形 (nccs_params->rgrc[1]
和nccs_params->rgrc[2]
)通过 Windows,至少根据 MSDN 页面中非常简短的 doxWM_NCCALCSIZE
和NCCALCSIZE_PARAMS
。 Windows 10 可能对该返回值更加严格;我会尝试一下。然而,即使我们假设我们可以说服 Windows 10 不要在
SetWindowPos()
中执行BitBlt
操作,结果还是出现了一个新问题...Windows 10(也可能是还有 Windows 8)在 XP/Vista/7 的旧传统骚扰之上添加了另一层客户区骚扰。
在 Windows 10 下,应用程序不会直接绘制到帧缓冲区,而是绘制到 Aero 窗口管理器 (DWM.exe) 合成的屏幕外缓冲区中。
事实证明,DWM 有时会决定通过在您的客户区域上绘制自己的内容来“帮助”您(有点像
BitBlt
,但更反常,甚至进一步超出您的控制)。因此,为了避免客户区干扰,我们仍然需要控制
WM_NCCALCSIZE
,但我们还需要防止 DWM 干扰您的像素。我正在与完全相同的问题作斗争,并创建了一个综述问题/答案,其中汇集了 10 年有关该主题的帖子,并提供了一些新的见解(太长,无法将内容粘贴到该问题中)。从 Windows Vista 开始,上述 BitBlt 不再是唯一的问题。享受:
如何平滑丑陋的抖动/调整窗口大小时闪烁/跳跃,尤其是拖动左/上边框(Win 7-10;bg、bitblt 和 DWM)?
Here's a 2018 update, since I just ran through the very same gauntlet as you.
The "final solution" in your question, and the related answers, that mention tricks with
WM_NCCALCSIZE
andCS_HREDRAW|CS_VREDRAW
are good for preventing Windows XP/Vista/7 from doing theBitBlt
that molests your client area during resizing. It might even be useful to mention a similar trick: you can interceptWM_WINDOWPOSCHANGING
(first passing it ontoDefWindowProc
) and setWINDOWPOS.flags |= SWP_NOCOPYBITS
, which disables theBitBlt
inside the internal call toSetWindowPos()
that Windows makes during window resizing. This has the same eventual effect of skipping theBitBlt
.And some people mentioned that your
WM_NCCALCSIZE
trick no longer works in Windows 10. I think that might be because the code you wrote returnsWVR_ALIGNLEFT|WVR_ALIGNTOP
when it should be returningWVR_VALIDRECTS
in order for the two rectangles you constructed (nccs_params->rgrc[1]
andnccs_params->rgrc[2]
) to be used by Windows, at least according to the very skimpy dox in the MSDN pages forWM_NCCALCSIZE
andNCCALCSIZE_PARAMS
. It's possible that Windows 10 is more strict about that return value; I would try it out.However, even if we assume that we can convince Windows 10 not to do
BitBlt
insideSetWindowPos()
, it turns out there's a new problem...Windows 10 (and possibly also Windows 8) adds another layer of client area molestation on top of the old legacy molestation from XP/Vista/7.
Under Windows 10, apps do not draw directly to the framebuffer, but instead draw into offscreen buffers that the Aero Window manager (DWM.exe) composites.
It turns out that DWM will sometimes decide to "help" you by drawing its own content over your client area (sort of like a
BitBlt
but even more perverse and even further out of your control).So in order to be free of client area molestation, we still need to get
WM_NCCALCSIZE
under control but we also need to prevent DWM from messing with your pixels.I was fighting with exactly the same problem and created a roundup Question/Answer which brings together 10 years of posts on this topic and offers some new insights (too long to paste the content here in this question). The BitBlt mentioned above is no longer the only problem, as of Windows Vista. Enjoy:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?
对于某些控件,可以使用WM_PRINT消息使控件绘制到DC中。但这并不能真正解决您的主要问题,即您希望 Windows 在调整大小期间不绘制任何内容,而是让您完成所有操作。
答案是,只要有子窗口,你就无法做你想做的事。
我最终在自己的代码中解决这个问题的方法是切换到使用 无窗口控件。由于它们没有自己的窗口,因此它们总是与其父窗口同时绘制(并进入同一个 DC)。这允许我使用简单的双缓冲来完全消除闪烁。当我需要时,我什至可以通过不调用父级绘制例程中的绘制例程来简单地抑制子级的绘制。
这是我所知道的在调整大小操作期间完全消除闪烁和撕裂的唯一方法。
For some controls, you can use WM_PRINT message to make the control draw into a DC. But that doesn't really solve your primary problem, which is that you want Windows to NOT draw anything during resize, but to let you do it all.
And the answer is that you just can't do what you want as long as you have child windows.
The way I ended up solving this eventually in my own code is to switch to using Windowless Controls. Since they have no window of their own, they always draw at the same time (and into the same DC) as their parent window. This allows me to use simple double buffering to completely remove flicker. I can even trivially suppress painting of the children when I need to just by not calling their draw routine inside the parent's draw routine.
This is the only way I know of to completely get rid of flicker and tearing during resize operations.
如果您能找到插入的地方,
CWnd::LockWindowUpdates()
将阻止任何绘图发生,直到您解锁更新为止。但请记住,这是一种黑客攻击,而且是一种相当丑陋的黑客攻击。调整大小时你的窗口看起来会很糟糕。如果您遇到的问题是在调整大小期间闪烁,那么最好的办法是诊断闪烁,而不是通过遮挡油漆来隐藏闪烁。
需要注意的一件事是在调整大小期间调用过于频繁的重绘命令。如果您的窗口控件正在调用
RedrawWindow()
指定了RDW_UPDATENOW
标志后,它将立即重新绘制。但是您可以删除该标志并指定 RDW_INVALIDATE 来代替,这会告诉控件使窗口无效而无需重新绘制。它会在闲置时重新绘制,使显示保持新鲜,而不会显得花哨。If you can find a place to plug it in,
CWnd::LockWindowUpdates()
will prevent any drawing from occuring until after you unlock the updates.But keep in mind this is a hack, and a fairly ugly one at that. Your window will look terrible during resizes. If the problem you are having is flickering during resizes, then the best thing to do is diagnose the flickering, rather than hiding the flickering by blocking paints.
One thing to look for are redraw commands that get called too often during the resize. If you r window's controls are calling
RedrawWindow()
with theRDW_UPDATENOW
flag specified, it is going to repaint then and there. But you can strip out that flag and specifyRDW_INVALIDATE
instead, which tells the control to invalidate the window without repainting. It will repaint at idle time, keeping the display fresh without spazzing out.有多种方法,但我发现唯一可以普遍使用的方法是双缓冲:绘制到屏幕外缓冲区,然后将整个缓冲区传输到屏幕。
Vista Aero 及更高版本中免费提供此功能,因此您的痛苦可能是短暂的。
我不知道 XP 下 Windows 和系统控件的通用双缓冲实现,但是,这里有一些需要探索的内容:
Keith Rule 的 CMemDC 用于双缓冲您用 GDI 自己绘制的任何内容
WS_EX_COMPOSITED 窗口样式(请参阅备注部分,以及stackoverflow上的内容)
There are various approaches, but I found the only one that can be used generally is double buffering: draw to an offscreen buffer, then blit the entire buffer to screen.
That comes for free in Vista Aero and above, so your pain might be shortlived.
I am not aware of a general double-buffering implementation for windows and system controls under XP, However, here are some things to explore:
Keith Rule's CMemDC for double-buffering anything you draw yourself with GDI
WS_EX_COMPOSITED Window style (see the remarks section, and something here on stackoverflow)
只有一种方法可以有效诊断重画问题——远程调试。
购买第二台电脑。在其上安装 MSVSMON。添加构建后步骤或实用程序项目,将构建产品复制到远程 PC。
现在您应该能够在 WM_PAINT 处理程序、WM_SIZE 处理程序等中放置断点,并在执行大小和重绘时实际跟踪对话框代码。如果您从 MS 符号服务器下载符号,您将能够看到完整的调用堆栈。
一些放置得很好的断点 - 在您的 WM_PAINT、WM_ERAGEBKGND 处理程序中,您应该很清楚为什么您的窗口在 WM_SIZE 周期的早期被同步重绘。
系统中有很多窗口由父窗口和分层子控件组成 - 资源管理器窗口非常复杂,包含列表视图、树视图预览面板等。资源管理器在调整大小时不存在闪烁问题,因此很可能获得父窗口的无闪烁调整大小:-您需要做的是捕获重绘,找出导致它们的原因,然后确保消除原因。
there is only one way to effectively diagnose repainting problems - remote debugging.
Get a 2nd PC. Install MSVSMON on it. Add a post build step or utility project that copies your build products to the remote PC.
Now you should be able to place breakpoints in WM_PAINT handlers, WM_SIZE handlers and so on and actually trace through your dialog code as it performs the size and redraw. If you download symbols from the MS symbol servers you will be able to see full call stacks.
Some well placed breakpoints - in your WM_PAINT, WM_ERAGEBKGND handlers and you should have a good idea of why your window is being synchronously repainted early during the WM_SIZE cycle.
There are a LOT of windows in the system that consist of a parent window with layered child controls - explorer windows are massivly complicated with listviews, treeviews preview panels etc. Explorer does not have a flicker problem on resizing, so It is celarly possible to get flicker free resizing of parent windows :- what you need to do is catch the repaints, figure out what caused them, and, well, ensure that the cause is removed.
似乎有效的方法:
在我使用 Aero 在 Windows 7 下进行的测试中,这非常接近完美。
What appears to work:
This is very close to perfect, in my testing under Windows 7 with Aero.