如何仅绘制窗口标题栏?使用WM_NCPAINT出现异常如何解决?
问题根因
WM_NCPAINT绘制API使用错误:BeginPaint/EndPaint是专门用于处理WM_PAINT消息、仅返回客户区绘制上下文的API,你用这个获取到的DC本身只对应客户区,自然会绘制到客户区上,非客户区绘制应该调用GetWindowDC获取整个窗口的DC,绘制完成后调用ReleaseDC释放。- 没有调用基类的
OnNcPaint实现:你重写了OnNcPaint但完全跳过了MFC基类默认的非客户区绘制逻辑,系统默认的标题栏、边框绘制逻辑都没有执行,所以启动时标题栏直接消失。 - Win10风格标题栏回退到Win7风格的原因:Win10及以上系统的默认现代化标题栏是由DWM(桌面窗口管理器)负责绘制的,自定义处理
WM_NCPAINT且没有兼容DWM绘制逻辑时,系统会自动禁用当前窗口的DWM非客户区渲染,回退到Win7时代的GDI绘制风格。
修复方案
你可以参考以下修改后的代码实现自定义非客户区绘制:
class CMainFrame : public CFrameWnd { public: CMainFrame() { Create( NULL, _T("Hello World!"), WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, CRect(CPoint(100, 100), CSize(640, 360))); } protected: afx_msg void OnNcPaint(); DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_NCPAINT() END_MESSAGE_MAP() void CMainFrame::OnNcPaint() { // 先调用基类实现完成默认非客户区绘制,若要完全自定义标题栏可删除此行自行实现全部绘制逻辑 CFrameWnd::OnNcPaint(); // 获取包含非客户区的全窗口DC CDC* pDC = GetWindowDC(); CBrush brush; brush.CreateSolidBrush(RGB(0, 0, 255)); // 计算标题栏区域范围,避免绘制到客户区 CRect ncRect; GetWindowRect(&ncRect); ncRect.OffsetRect(-ncRect.left, -ncRect.top); // 转换为窗口DC对应的相对坐标 ncRect.bottom = ncRect.top + GetSystemMetrics(SM_CYCAPTION); // 仅保留标题栏高度的区域 pDC->FillRect(&ncRect, &brush); // 释放DC资源 ReleaseDC(pDC); } class CApplication : public CWinApp { BOOL InitInstance() override { CMainFrame* mainWnd = new CMainFrame(); m_pMainWnd = mainWnd; mainWnd->ShowWindow(SW_NORMAL); mainWnd->UpdateWindow(); return TRUE; } };
- 如果你要完全自定义标题栏、不需要系统默认绘制,可以删除基类调用,同时需要自行处理所有非客户区元素的绘制(标题文字、最小化/关闭按钮、边框等),并且需要处理
WM_NCCALCSIZE消息调整客户区范围,避免自定义的标题栏和客户区内容重叠。 - 如果需要保留Win10/Win11的现代化标题栏风格,建议不要直接重写
WM_NCPAINT,改为使用DWM相关API扩展自定义标题栏内容。
内容的提问来源于stack exchange,提问作者lynx pravoka
相关产品推荐
相关产品推荐

