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))
CaptureWindow();
{
CWindowDC dcWnd(m_pWnd);
dcWnd.StretchBlt(0, 0, rcWnd.Width(), rcWnd.Height(),
&m_dcCapture, 0, 0, m_rcCapture.Width(),
m_rcCapture.Height(), SRCCOPY);
}
m_pWnd->SetRedraw(FALSE);
ret = CallWindowProc((WNDPROC)m_hPrevProc, hwnd, uMsg, wParam, lParam);
m_pWnd->SetRedraw(TRUE);
CaptureWindow();
{
CWindowDC dcWnd(m_pWnd);
dcWnd.BitBlt(0, 0, rcWnd.Width(), rcWnd.Height(),
&m_dcCapture, 0, 0, SRCCOPY);
}
m_rcWnd = rcWnd;
}
else
if(!m_bResizing)
ret = CallWindowProc((WNDPROC)m_hPrevProc, hwnd, uMsg, wParam, lParam);
return ret;
}
void CMainFrameResize::CaptureWindow()
{
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);