窗口重绘优化技术

在开发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_HREDRAWCS_VREDRAW这两个窗口类样式会导致窗口重绘,即使主框架窗口已经接收到SetRedraw(FALSE)。它们也是调整大小时闪烁的原因之一。因此,需要从应用程序的所有窗口中移除这两个样式。

对于自定义的CWnd派生类,可以在PreCreateWindow函数中确保不传递CS_HREDRAWCS_VREDRAW。对于现有的类,如CToolBarCStatusBar,可以使用以下模板类来移除这两个样式:

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);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485