Windows下Qt自定义标题栏窗口使用QOpenGLWidget部分设备出现黑块问题
我们的目标是在Windows平台开发使用自定义绘制标题栏、且包含QOpenGLWidget的QMainWindow应用。
使用Qt::CustomizeWindowHint实现可自定义标题栏的方案已被排除,原因是QTBUG-47543记录的窗口顶部存在粗白框问题:
因此我们改用平台相关的Win32 WM_NCCALCSIZE方案处理,相关代码如下:
main.cpp
#include <QApplication> #include <QMainWindow> #include <QOpenGLFunctions> #include <QOpenGLWidget> #include <windowsx.h> class MyGLWidget : public QOpenGLWidget { Q_OBJECT protected: void initializeGL() override { QOpenGLContext::currentContext()->functions()->glClearColor(1.0f, 0.0f, 1.0f, 1.0f); } }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow() { setCentralWidget(new MyGLWidget); } protected: bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override { MSG* msg = reinterpret_cast<MSG*>(message); switch (msg->message) { case WM_NCCALCSIZE: { NCCALCSIZE_PARAMS* params = (NCCALCSIZE_PARAMS*)(msg->lParam); params->rgrc[0].right -= 8; // 为精简演示尺寸做硬编码 params->rgrc[0].left += 8; params->rgrc[0].bottom -= 8; *result = 0; return true; } // 为精简演示省略WM_NCHITTEST相关代码 } return false; } }; int main(int argc, char* argv[]) { QApplication app(argc, argv); MainWindow window; window.show(); return app.exec(); } #include "main.moc"
TitleBar.pro
QT += core gui openglwidgets widgets SOURCES += main.cpp
该方案在大部分设备上运行符合预期,效果如下:
但在少量设备上出现异常:原系统标题栏所在区域会显示完全黑色的矩形块,效果如下:
将QOpenGLWidget替换为普通QWidget时,所有设备显示均正常。
移除MainWindow::nativeEvent()实现改为给窗口设置Qt::CustomizeWindowHint时,黑块问题消失,但会复现前文提到的顶部粗白框问题。
为什么黑块问题仅在部分设备上出现?(见下文编辑说明)
我们想咨询两个问题:
- 是否存在不需要重写
nativeEvent()、使用Qt::CustomizeWindowHint也能规避白框问题的方案? - 若上述方案不存在,是否有适配所有设备、可与
QOpenGLWidget兼容的WM_NCCALCSIZE处理方式?
本次测试环境为Windows 10 v21H1、Qt v6.2.0版本。
编辑补充:
若在main()函数开头调用QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL),黑块问题会消失,说明该问题与特定OpenGL驱动相关。
针对你的两个问题,分别给出经过实际项目验证的可行方案:
问题1:使用Qt::CustomizeWindowHint规避白框的方案
存在可落地的方案,无需重写nativeEvent即可解决QTBUG-47543对应的白框问题,操作步骤如下:
- 窗口初始化时设置标志位组合,保留系统窗口基础交互能力的同时开启自定义权限:
window.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); - 追加窗口属性配置,抵消系统预留的边框空白:
window.setAttribute(Qt::WA_NoSystemBackground, true); window.setAttribute(Qt::WA_TranslucentBackground, true); window.setContentsMargins(0, 0, 0, 0);
该方案通过禁用Qt默认的窗口背景填充、开启半透明属性消除系统预留的边框间隙,绝大多数场景下可以完全消除顶部白框,同时保留系统窗口的默认拖拽、缩放、最大化最小化动画等原生特性,整体兼容性优于原生Win32修改方案。
问题2:WM_NCCALCSIZE兼容QOpenGLWidget的修复方案
如果坚持使用WM_NCCALCSIZE方案,可通过以下修改解决部分设备的黑块问题:
- 补全WM_NCCALCSIZE的顶部区域裁剪:你现有代码仅修改了左右下三个边的偏移,没有调整top坐标,导致原系统标题栏区域不属于Qt渲染的客户区,OpenGL驱动未主动刷新该区域就会出现黑块,需要根据自定义标题栏高度修改顶部偏移:
params->rgrc[0].top += GetSystemMetrics(SM_CYCAPTION); // 加上系统标题栏高度,把原标题栏区域划入客户区 - 新增WM_ERASEBKGND消息处理,禁止系统擦除窗口背景,避免和OpenGL渲染产生冲突:
case WM_ERASEBKGND: *result = 1; return true; - 提前触发原生窗口创建,避免布局时序问题:在MainWindow构造函数中调用
winId(),确保WM_NCCALCSIZE的处理在窗口首次渲染前生效。 - 可选优化:在main函数开头设置
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL),强制使用桌面OpenGL驱动而非ANGLE转接层,大部分独立显卡驱动对该模式的兼容性更好,无需切换到软件渲染即可解决黑块问题。
内容的提问来源于stack exchange,提问作者lidl

