如果我们希望一个窗口覆盖用户的整个桌面,此时就要考虑用户有多个屏幕的场景(此窗口要横跨多个屏幕),由于每个屏幕的分辨率和缩放比例可能是不同的,Qt底层在为此窗口设置缩放比例(DevicePixelRatio)时出了问题。
复现环境:
-
屏幕A:最大可用分辨率:3840*2160
-
屏幕A:当前设置分辨率:2048*1080
-
屏幕A:缩放比例:100%
-
屏幕B:最大可用分辨率:2560*1440
-
屏幕B:当前设置分辨率:2560*1440
-
屏幕B:缩放比例:125%
注:其他条件不变的情况下:只要屏幕A的当前设置分辨率比B小,均会出错;其他条件不变的情况下:只要屏幕A的当缩放比例与B不同,亦均会出错。
复现步骤:
1、设置窗口跨屏的代码
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)
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来创建窗口:
hwnd = CreateWindowEx(exStyle,
L"ScreenCapture", L"ScreenCapture",
style,x, y, w, h,
NULL, NULL, hinstance,
NULL);
然后把 QImage 渲染到这个原生窗口中:
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帮我创建窗口。