在开发Windows应用程序时,窗口重绘是一个常见的问题。当窗口大小发生变化时,尤其是从顶部或左侧角拖动调整大小时,用户可能会遇到窗口内容闪烁的问题。这种现象不仅影响用户体验,还可能降低应用程序的性能。本文将探讨这个问题的原因,并提供一种解决方案,以优化窗口重绘过程,减少闪烁现象。
当窗口大小增加时,Windows操作系统会自动在窗口的左上角绘制旧内容,然后发送WM_WINDOWPOSCHANGED
消息。这个消息会导致发送WM_SIZE
消息给所有子窗口,以及其他一些需要时间处理的消息。只有经过一段时间后,旧内容才会被客户端窗口擦除。例如,状态栏在调整大小时可能会上下跳动。
为了解决这个问题,可以使用一个名为CMainFrameResize
的类,该类在窗口调整大小时捕获窗口的截图。在接收到WM_WINDOWPOSCHANGED
消息后,它会使用StretchBlt
函数将旧窗口内容拉伸到新窗口上。这样,用户会感觉到窗口立即调整大小,几乎看不到闪烁。以下是实现这一功能的代码:
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;
}
在CMainFrameResize::CaptureWindow
函数中,使用PrintWindow
函数捕获窗口内容。
void CMainFrameResize::CaptureWindow() {
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
函数中确保不传递CS_HREDRAW
和CS_VREDRAW
。对于现有的类,如CToolBar
和CStatusBar
,可以使用以下模板类来移除这两个样式:
template
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 m_wndStatusBar;
CWndNoCSHVRedraw m_wndToolBar;
并且,可以将视图从该类派生(而不是简单地从CView
派生):
class CTestJitterView : public CWndNoCSHVRedraw {
...
};
return CWndNoCSHVRedraw::PreCreateWindow(cs);