Click here to Skip to main content
15,889,858 members
Articles / Desktop Programming / MFC

Flicker Free Main Frame Resizing

Rate me:
Please Sign up or sign in to vote.
1.75/5 (17 votes)
4 Oct 2008CPOL2 min read 66.4K   893   11   19
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:

C++
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:

C++
#include "MainFrmResize.h"

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

And inside CMainFrame::OnCreate:

C++
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:

C++
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:

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

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

C++
class CTestJitterView : public CWndNoCSHVRedraw<CView> 

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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Israel Israel

Comments and Discussions

 
GeneralMy vote of 1 Pin
skyformat99@gmail.com4-Aug-13 23:23
skyformat99@gmail.com4-Aug-13 23:23 
GeneralMy vote of 1 Pin
Jeroen Walter22-Dec-11 8:26
Jeroen Walter22-Dec-11 8:26 
GeneralMy vote of 1 Pin
NetDave26-May-09 18:22
NetDave26-May-09 18:22 
GeneralMy vote of 1 Pin
Paul Vickery21-Jan-09 3:49
professionalPaul Vickery21-Jan-09 3:49 
GeneralInvalidate(FALSE); Pin
Shao Voon Wong6-Oct-08 22:15
mvaShao Voon Wong6-Oct-08 22:15 
GeneralRe: Invalidate(FALSE); Pin
David Lantsman7-Oct-08 10:02
David Lantsman7-Oct-08 10:02 
QuestionDemo? Elaborate on implementation? Pin
Damir Valiulin4-Oct-08 19:09
Damir Valiulin4-Oct-08 19:09 
AnswerRe: Demo? Elaborate on implementation? Pin
David Lantsman5-Oct-08 7:55
David Lantsman5-Oct-08 7:55 
GeneralRe: Demo? Elaborate on implementation? Pin
Damir Valiulin5-Oct-08 10:12
Damir Valiulin5-Oct-08 10:12 
I really didn't mean my previous post to be an attack, I just wanted to suggest why the article recieved (and continues ro recieve) bad rating.

You are correct to point out that I was wrong about PrintWindow. It was long time since I looked at that API and for some reason it stuck in my head that it used WM_PRINT inernally, which is not true.

My comment about developers being lazy is half joke. The main problem is that everyone is pressed for time. Unless there's a really compelling reason, I rarely download raw source code with intention of compiling a sample just to see how it works. If there's no executable I normally pass.

But because I didn't want to answer your post without trying your code in action, I actually took some time to integrate your class into one of our company's existing MDI apps. Here are my findings:

1. PrintWindow is only available on XP or higher. Support for Win 2000 has not been officially canned yet by MS, so we need to support it too. You should mention this compatibility issue in your article.
2. The amount of jittering was actually worse than without the class!!! The toolbars kept on changing their size and view windows were jumping up and down... Dead | X|

To tell you honestly I never really found small flicker during resizing to be a problem. Couple of exceptions are: OpenGL windows, and FormView windows.

Problem with form view windows is the same problem as with dialog windows: when dialog redraws its background in response to WM_SIZE, it paints over controls and this produces lots of flicker. I haven't been able to solve this problem 100%. You could minimize it by overriding OnEraseBkgnd and clipping out areas under certain controls, but it's not fool proof.

Regards,
Damir Valiulin
GeneralRe: Demo? Elaborate on implementation? Pin
Damir Valiulin5-Oct-08 10:24
Damir Valiulin5-Oct-08 10:24 
GeneralRe: Demo? Elaborate on implementation? Pin
PJ Arends5-Oct-08 11:08
professionalPJ Arends5-Oct-08 11:08 
GeneralRe: Demo? Elaborate on implementation? Pin
David Lantsman5-Oct-08 14:38
David Lantsman5-Oct-08 14:38 
GeneralRe: Demo? Elaborate on implementation? Pin
Damir Valiulin6-Oct-08 6:19
Damir Valiulin6-Oct-08 6:19 
GeneralRe: Demo? Elaborate on implementation? Pin
David Lantsman6-Oct-08 8:14
David Lantsman6-Oct-08 8:14 
JokeRe: Demo? Elaborate on implementation? Pin
Damir Valiulin6-Oct-08 14:42
Damir Valiulin6-Oct-08 14:42 
GeneralRe: Demo? Elaborate on implementation? Pin
David Lantsman6-Oct-08 16:51
David Lantsman6-Oct-08 16:51 
GeneralRe: Demo? Elaborate on implementation? Pin
Kochise7-Oct-08 9:22
Kochise7-Oct-08 9:22 
GeneralRe: Demo? Elaborate on implementation? Pin
David Lantsman7-Oct-08 9:42
David Lantsman7-Oct-08 9:42 
GeneralRe: Demo? Elaborate on implementation? Pin
David Lantsman5-Oct-08 14:56
David Lantsman5-Oct-08 14:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.