Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Flicker Free Main Frame Resizing

0.00/5 (No votes)
4 Oct 2008 1  
Flicker free resizing of the main frame window

Problem

All frame windows in Windows flicker when they are resized, especially from the top/left corners.

Reason

When a window's size is increased during resizing, Windows automatically draws the old contents over the top left part of the window, then sends the WM_WINDOWPOSCHANGED message to the window, which ends up sending WM_SIZE to all the children in addition to a bunch of other messages which take some time to be processed. Only after a while are the old contents erased by some client window.

As a consequence, for example, it feels like the status bar of the frame window jumps up and down during resizing.

Solution

The class CMainFrameResize contains a screenshot of the frame window during resizing. Right after WM_WINDOWPOSCHANGED is received, it stretches the old window contents over the new window (using StretchBlt, because the new window size may be larger or smaller than the captured window). This way, there is an illusion of immediate resizing, and to the eye, it appears with almost no flickering. Here is the main code that does this:

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);
}

Using the code

Include the following variable in CMainFrame:

#include "MainFrmResize.h"

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

And inside CMainFrame::OnCreate:

m_resize.Attach(this);

CS_HREDRAW and CS_VREDRAW

These two window class styles make a window repaint itself during resizing even if the main frame window has received SetRedraw(FALSE). They are also responsible for some of the flickering during resizing. Therefore, they have to be removed from all windows in the application.

Windows that you create on your own in custom CWnd derived classes can override PreCreateWindow and make sure that they do not pass CS_HREDRAW and CS_VREDRAW in AfxRegisterWndClass. The problem comes up when you need to remove these two class styles from existing classes like CToolBar and CStatusBar. Here is a template class that does exactly that:

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;
    };
};

In the sample application, inside MainFrm.h, we write:

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

and also we derive the view from that class (instead of simply from CView):

class CTestJitterView : public CWndNoCSHVRedraw<CView> 

and in CTestJitterView::PreCreateWindow (or we could remove the overridden PreCreateWindow function altogether):

return CWndNoCSHVRedraw<CView>::PreCreateWindow(cs); 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here