Qt跨屏窗口的一个Bug及解决方案

如果我们希望一个窗口覆盖用户的整个桌面,此时就要考虑用户有多个屏幕的场景(此窗口要横跨多个屏幕),由于每个屏幕的分辨率和缩放比例可能是不同的,Qt底层在为此窗口设置缩放比例(DevicePixelRatio)时出了问题。

复现环境

  • 屏幕A:最大可用分辨率:3840*2160

  • 屏幕A:当前设置分辨率:2048*1080

  • 屏幕A:缩放比例:100%

  • 屏幕B:最大可用分辨率:2560*1440

  • 屏幕B:当前设置分辨率:2560*1440

  • 屏幕B:缩放比例:125%

注:其他条件不变的情况下:只要屏幕A的当前设置分辨率比B小,均会出错;其他条件不变的情况下:只要屏幕A的当缩放比例与B不同,亦均会出错。

复现步骤

1、设置窗口跨屏的代码

cpp 复制代码
window->setFlags(window->flags() | Qt::FramelessWindowHint);
auto hwnd = (HWND)window->winId();
auto x = GetSystemMetrics(SM_XVIRTUALSCREEN);
auto y = GetSystemMetrics(SM_YVIRTUALSCREEN);
auto w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
auto h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
SetWindowPos(hwnd,HWND_TOP,   
    x, y,       
    w, h,       
    SWP_NOZORDER );
PostMessage(hwnd, WM_DISPLAYCHANGE, 0, 0);

2、为用户桌面拍照(把这个照片显示在窗口中,照片布满整个窗口,就能只管的看出此Bug)

cpp 复制代码
HDC hScreen = GetDC(NULL);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
DeleteObject(SelectObject(hDC, hBitmap));
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hScreen, x, y, SRCCOPY);
img = QImage(w, h, QImage::Format_ARGB32);
BITMAPINFO bmi = { sizeof(BITMAPINFOHEADER), (long)w, 0 - (long)h, 1, 32, BI_RGB, (DWORD)w * 4 * h, 0, 0, 0, 0 };
GetDIBits(hDC, hBitmap, 0, h, img.bits(), &bmi, DIB_RGB_COLORS);
DeleteDC(hDC);
DeleteObject(hBitmap);
ReleaseDC(NULL, hScreen);

注:这段代码中img就是拍照后得到的QImage对象,如何把图像渲染到窗口中的代码就不写了。

这是出错时的样子:

(如你所见,左边屏幕的内容已经侵入右边屏幕去了)

这是正常时的样子:

问题影响范围

这个问题自 Qt5.x.x 到前天刚发布的 Qt6.8.2 一直存在。

无论是 Qt Widgets 窗口还是 Qt Quick 窗口,都有这个问题。

解决方案(思路)

如果是 Qt Widgets窗口 ,那么你就直接用系统API来创建窗口:

cpp 复制代码
hwnd = CreateWindowEx(exStyle,
L"ScreenCapture", L"ScreenCapture",
style,x, y, w, h, 
NULL, NULL, hinstance, 
NULL);

然后把 QImage 渲染到这个原生窗口中:

cpp 复制代码
if (img.isNull()) return;
HDC hdc = GetDC(hwnd);
auto compDC = CreateCompatibleDC(NULL);
auto bitmap = CreateCompatibleBitmap(hdc, w, h);
DeleteObject(SelectObject(compDC, bitmap));

BITMAPINFO info = { sizeof(BITMAPINFOHEADER), w, 0 - h, 1, 32, BI_RGB, w * 4 * h, 0, 0, 0, 0 };
SetDIBits(hdc, bitmap, 0, h, img.bits(), &info, DIB_RGB_COLORS);
BLENDFUNCTION blend = { .BlendOp{AC_SRC_OVER}, .SourceConstantAlpha{255}, .AlphaFormat{AC_SRC_ALPHA} };
POINT pSrc = { 0, 0 };
SIZE sizeWnd = { w, h };
UpdateLayeredWindow(hwnd, hdc, NULL, &sizeWnd, compDC, &pSrc, NULL, &blend, ULW_ALPHA);
ReleaseDC(hwnd, hdc);

DeleteDC(compDC);
DeleteObject(bitmap);

然后所有的绘图操作都在这个QImage上进行。

如果是 Qt Quick 窗口,那么就要用 QQuickRenderControl 来把QML内容渲染到原生窗口中了,代码过于复杂,这里就不贴了,详情请参考:https://doc.qt.io/qt-6/qquickrendercontrol.html

总之:就是不要让Qt帮我创建窗口。

相关推荐
qq_544329175 天前
CRM项目的开发与调试整体策略
前端·后端·bug
程序员小寒9 天前
由于请求的竞态问题,前端仔喜提了一个bug
前端·javascript·bug
Golinie9 天前
记一次Linux共享内存段排除Bug:key值为0x0000000的共享内存段删除不了
linux·bug·共享内存段
谢尔登10 天前
【Bug 记录】el-sub-menu 第一次进入默认不高亮
bug
qq_5443291712 天前
下载一个项目到跑通的大致过程是什么?
javascript·学习·bug
Algorithm-00712 天前
软件测试入门—软件缺陷 Bug 详解
软件测试·bug
从int开始13 天前
加速排查线上bug
bug
LilySesy13 天前
【业务案例】F.13——SAP系统标准的清帐程序有BUG?
运维·bug·sap·abap·esb·internet服务