当用户调整对话框大小时,如何强制Windows不要在对话框中重绘任何内容? [英] How do I force windows NOT to redraw anything in my dialog when the user is resizing my dialog?

查看:136
本文介绍了当用户调整对话框大小时,如何强制Windows不要在对话框中重绘任何内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当用户抓住可调整大小的窗口的一个角然后移动它时,窗口首先会移动窗口的内容,然后向正在调整大小的窗口发出WM_SIZE.

因此,在一个对话框中,我想控制各种子控件的移动,并且要消除闪烁,用户首先会看到Windows OS认为该窗口的外观(因为AFAICT,该OS使用bitblt在发送WM_SIZE之前在窗口内移动内容的方法)-只有然后,我的对话框才能处理其子控件的移动或调整其大小等,之后它必须强制将其移动到重新绘制,现在至少会引起闪烁.

我的主要问题是:有没有办法强迫窗口不要执行这种愚蠢的bitblt事情?对于带有随窗口移动的控件的窗口而言,这肯定是错误的调整大小,或者在调整其父级时调整自身大小.无论哪种方式,让OS进行预涂都只是拧螺丝.

一段时间以来,我认为它可能与CS_HREDRAW和CSVREDRAW类标志有关.但是,现实是我不希望操作系统要求我擦除窗口-我只想自己重新粉刷,而无需操作系统先更改窗口的内容(即我希望显示的是原来的样子)在用户开始调整大小之前-操作系统没有任何改动).而且我也不希望操作系统告诉每个控件都需要重绘(除非它恰好是被调整大小掩盖或揭示的控件).

我真正想要的是

  1. 移动&调整子控件的大小 之前 ,所有内容都会在屏幕上更新.
  2. 完全绘制所有已移动或调整大小的子控件,以使它们在没有新尺寸&的情况下不会出现伪像.位置.
  3. 在子控件之间绘制空间,而不会影响子控件本身.

注意:步骤2和3可以相反.

当我将DeferSetWindowPos()与标记为WS_CLIPCHILDREN的对话框资源结合使用时,上述三件事似乎正确发生.

如果可以对内存DC执行上述操作,然后在WM_SIZE处理程序的末尾仅执行一次bitblt,我将获得一个额外的小好处.

我已经玩了一段时间了,我无法逃避两件事:

  1. 我仍然无法抑制Windows执行预测性bitblt". 答案:有关覆盖WM_NCCALCSIZE以禁用此行为的解决方案,请参见下文.

  2. 我看不到如何建立一个对话框,其子控件绘制到双缓冲区. 答案:有关如何要求Windows OS双重缓冲对话框的信息,请参见下面的John的答案(标记为答案)(注意:根据文档,这不允许在绘制操作之间使用任何GetDC()).


我的最终解决方案(谢谢所有贡献者,尤其是John K.):

经过大量的汗水和眼泪,我发现以下技术在Aero和XP或禁用Aero的情况下均能完美工作.不存在滑动(1).

  1. 挂钩对话框.
  2. 重写WM_NCCALCSIZE强制Windows验证整个客户端区域,而不是盲目的任何操作.
  3. 覆盖WM_SIZE来完成您的所有举动&对于所有可见窗口,使用BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos调整大小.
  4. 确保对话框窗口具有WS_CLIPCHILDREN样式.
  5. 请勿使用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等). /p>

如果有足够的兴趣,可以说服我使用CodeProject.com或类似地方的真实示例实现来编辑我的发现.给我发消息.

(1)请注意,不可能避免在过去的任何东西的上方绘制一面平局.对于对话框中未更改的每个部分,用户看不到任何内容(无闪烁).但是,如果事情发生了变化,用户就会看到一个变化-这是无法避免的,而且是100%的解决方案.

解决方案

您不能在调整大小时阻止绘画,但是可以(小心)阻止重新绘画,这是闪烁的来源.首先是bitblt.

有两种方法可以停止bitblt事情.

如果您拥有顶级窗口的类,则只需使用CS_HREDRAW | CS_VREDRAW样式进行注册.这将导致调整窗口大小来使整个工作区无效,而不是试图猜测哪些位将不会更改和位变.

如果您不拥有该类,但确实可以控制消息处理(对于大多数对话框为true). WM_NCCALCSIZE的默认处理是处理类样式CS_HREDRAWCS_VREDRAW的位置.默认行为是,当类具有CS_HREDRAW | CS_VREDRAW时,从处理WM_NCCALCSIZE返回WVR_HREDRAW | WVR_VREDRAW.

因此,如果可以拦截WM_NCCALCSIZE,则可以在调用DefWindowProc进行其他常规处理后强制返回这些值.

您可以收听WM_ENTERSIZEMOVEWM_EXITSIZEMOVE来了解何时调整窗口大小开始和停止,并使用它们临时禁用或修改图形和/或布局代码的工作方式,以最大程度地减少闪烁.您究竟想做什么来修改此代码,将取决于您在WM_SIZE WM_PAINTWM_ERASEBKGND中正常代码的正常工作.

绘制对话框的背景时,需要绘制任何子窗口的后面.确保对话框中有WS_CLIPCHILDREN即可解决此问题,因此您已经对此进行了处理.

当您移动子窗口时,请确保使用

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.

解决方案

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 styles CS_HREDRAW and CS_VREDRAW are handled, The default behavior is to return WVR_HREDRAW | WVR_VREDRAW from processing WM_NCCALCSIZE when the class has CS_HREDRAW | CS_VREDRAW.

So if you can intercept WM_NCCALCSIZE, you can force the return of these values after calling DefWindowProc to do the other normal processing.

You can listen to WM_ENTERSIZEMOVE and WM_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 in WM_SIZE WM_PAINT and WM_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 each SetWindowPos call.

这篇关于当用户调整对话框大小时,如何强制Windows不要在对话框中重绘任何内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆