65.9K
CodeProject 正在变化。 阅读更多。
Home

无闪烁主窗口调整大小

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.75/5 (16投票s)

2008年10月4日

CPOL

2分钟阅读

viewsIcon

68689

downloadIcon

933

无闪烁地调整主窗口大小。

问题

Windows 中的所有框架窗口在调整大小的时候都会闪烁,尤其是在从顶部/左上角调整的时候。

原因

当窗口大小在调整过程中增大时,Windows 会自动在窗口的左上部分绘制旧的内容,然后发送 WM_WINDOWPOSCHANGED 消息给窗口,这最终会向所有子窗口发送 WM_SIZE 消息,以及许多其他需要一些时间处理的消息。只有一段时间后,旧的内容才会被某个客户端窗口擦除。

因此,例如,在调整框架窗口大小时,状态栏会感觉上下跳动

解决方案

CMainFrameResize 包含在调整大小期间的框架窗口的屏幕截图。在收到 WM_WINDOWPOSCHANGED 消息后,它会**将旧窗口的内容拉伸到新窗口上**(使用 StretchBlt,因为新窗口的大小可能大于或小于捕获的窗口)。 这样,就产生了一种立即调整大小的错觉,并且在视觉上几乎没有闪烁。 这是实现此功能的关键代码:

LRESULT CMainFrameResize::OnWindowPosChanged(HWND hwnd, UINT uMsg, 
                                             WPARAM wParam, LPARAM lParam)
{
    LRESULT ret;
    CRect rcWnd;

    m_pWnd->GetWindowRect(&rcWnd);
    ret = 0;
    if(rcWnd.Size() != m_rcWnd.Size())
    {
        if(m_rcCapture == CRect(0, 0, 0, 0)) // capture for the first time 
            CaptureWindow();
    
        // first of all stretch the previous captured image to 
        // have something to show during the following lengthy operation 

        {
            CWindowDC dcWnd(m_pWnd);
            dcWnd.StretchBlt(0, 0, rcWnd.Width(), rcWnd.Height(), 
                             &m_dcCapture, 0, 0, m_rcCapture.Width(), 
                             m_rcCapture.Height(), SRCCOPY); 
        }
    
        // now wm_size is sent to all children, a lengthy operation 
        m_pWnd->SetRedraw(FALSE);
        ret = CallWindowProc((WNDPROC)m_hPrevProc, hwnd, uMsg, wParam, lParam);
        m_pWnd->SetRedraw(TRUE);

    
        // now get the new contents 
        CaptureWindow();

    
        // draw the new contents in one blit 
        {
            CWindowDC dcWnd(m_pWnd);
            dcWnd.BitBlt(0, 0, rcWnd.Width(), rcWnd.Height(), 
                         &m_dcCapture, 0, 0, SRCCOPY);
        }
    
        // update m_rcWnd 
        m_rcWnd = rcWnd;
    }
    else 
    if(!m_bResizing)
        ret = CallWindowProc((WNDPROC)m_hPrevProc, hwnd, uMsg, wParam, lParam);
    
    return ret; 

}

void CMainFrameResize::CaptureWindow()
{
    // use PrintWindow to capture the window to our dc
    m_pWnd->GetWindowRect(&m_rcCapture);
    m_pWnd->PrintWindow(&m_dcCapture, 0);
}

使用代码

CMainFrame 中包含以下变量

#include "MainFrmResize.h"

class CMainFrame : public CFrameWnd
{            
    ...
    CMainFrameResize m_resize;
}

并在 CMainFrame::OnCreate 内部

m_resize.Attach(this);

CS_HREDRAW 和 CS_VREDRAW

这两个窗口类样式使窗口在调整大小期间即使在主框架窗口收到 SetRedraw(FALSE) 后也会重新绘制自身。它们也负责调整大小期间的一些闪烁。 因此,必须从应用程序中的所有窗口中删除它们。

您可以在自定义的 CWnd 派生类中创建自己的窗口,并重写 PreCreateWindow,以确保它们不在 AfxRegisterWndClass 中传递 CS_HREDRAWCS_VREDRAW。 问题在于需要从现有的类(如 CToolBarCStatusBar)中删除这两个类样式。 这里有一个模板类可以做到这一点

template<class BaseClass>
class CWndNoCSHVRedraw : public BaseClass
{
public:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs)
    {
        WNDCLASSEX wc;
        ATOM atmRegister;
        if(GetClassInfoEx(NULL, cs.lpszClass, &wc))
        {
            if(wc.style & (CS_HREDRAW | CS_VREDRAW))
            {
                wc.cbSize = sizeof(wc);
                CString strClassNew;
                strClassNew.Format(_T("%sNOCSREDRAW"), wc.lpszClassName);
                wc.lpszClassName = strClassNew;
                wc.style &= ~(CS_HREDRAW | CS_VREDRAW);
                atmRegister = RegisterClassEx(&wc);
                ASSERT(atmRegister);
                cs.lpszClass = (LPCTSTR)atmRegister;
            }
        }
        else
            cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), 
            (HBRUSH) ::GetStockObject(WHITE_BRUSH), 
            ::LoadIcon(NULL, IDI_APPLICATION));

    cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
        
        cs.style |= WS_CLIPCHILDREN;
        if(!BaseClass::PreCreateWindow(cs))
            return FALSE;

        return TRUE;
    };
};

在示例应用程序中,在 MainFrm.h 内部,我们编写

CWndNoCSHVRedraw<CStatusBar> m_wndStatusBar;
CWndNoCSHVRedraw<CToolBar> m_wndToolBar;

并且我们也从该类派生视图(而不是直接从 CView 派生)

class CTestJitterView : public CWndNoCSHVRedraw<CView> 

并在 CTestJitterView::PreCreateWindow 中(或者我们可以完全删除重写的 PreCreateWindow 函数)

return CWndNoCSHVRedraw<CView>::PreCreateWindow(cs); 
© . All rights reserved.