近期因为工作设备的高分辨率,导致软件在运行时控件占比和正常的QtDesign里边所见不同,因此希望能解决高分屏的显示问题。
以下代码开启后,将启用高分辨率显示策略。
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
一开始看着效果还行,然后启用了一个无边框QWidget的功能。然后在多屏测试中发现点击事件触发的坐标和预期不符,尤其在多屏拖放中。
初版的无边框事件的识别,代码参考了feiyangqingyun大神的实现方式。
我这里获取指针相对坐标的方式是:
//计算相对坐标
long globX = GET_X_LPARAM(msg->lParam);
long globY= GET_Y_LPARAM(msg->lParam);
QPoint pos = mapFromGlobal(QPoint(x, y));
使用这个坐标来进行拉伸的判断,代码如下:
bool left = pos.x() < padding;
bool right = pos.x() > width() - padding;
bool top = pos.y() < padding;
bool bottom = pos.y() > height() - padding;
由于我在分屏中开发,为27寸 1920x1080分辨率,100%缩放比,所以我用着啥问题没有。
直到我拖进主屏中,发现这个pos的计算一塌糊涂。。。
我的笔记本是16寸 2560x1440分辨率,还开启了150%的缩放。
我打印了指针绝对坐标、相对坐标、窗体坐标,对比了下,然后检查之间的转换关系,有了一个发现。
在多屏显示中,坐标的计算还受到DPI缩放比的影响,也就是我们看到的系统设置中:
但是这个参数的获取,我搜了一圈,能获取的办法都不是很靠谱,获取逻辑、物理DPI是没法解决这个问题的。
查了很久,好像都不咋管用。这里边大部分都只是获取逻辑DPI,也就是默认96,不太好使。物理DPI虽然能获取到,但是没有另外一个参数用来计算DPI缩放比。我分屏试了下
物理DPI副屏
125% 65
100% 82
主屏
150% 126
125% 151
100% 189
我测了下,目前好像没法同时获取100%缩放比和其他缩放比的物理DPI,不通过这个比对没法得出正确的参数。
但是我在后续的开发中试了一种方法,能够成功获取。
前提是需要先启动上述的Qt高分屏功能,这个功能好像是会把原始的分辨率强行转换成所在显示屏的分辨率,基于这个特点,我们可以用Win下的系统函数,获取原始分辨率,然后和QT处理后的分辨率,计算出DPI缩放比。
列一下我们需要的步骤:
1.启动QT的高分屏功能
2.获取当前的屏幕
3.使用系统函数,获取当前的屏幕尺寸(原始)
4.获取当前屏幕尺寸(QT处理后)
5.DPI缩放比 = 原始尺寸/处理后尺寸
OK,开始实现
屏幕原始尺寸获取函数
//获取程序当前所在显示器的分辨率大小,可以动态的获取程序所在显示器的分辨率
SIZE GetScreenResolution(HWND hWnd) {
SIZE size;
if(!hWnd)
return size;
//MONITOR_DEFAULTTONEAREST 返回值是最接近该点的屏幕句柄
//MONITOR_DEFAULTTOPRIMARY 返回值是主屏幕的句柄
//如果其中一个屏幕包含该点,则返回值是该屏幕的HMONITOR句柄。如果没有一个屏幕包含该点,则返回值取决于dwFlags的值
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX miex;
miex.cbSize = sizeof(miex);
if (!GetMonitorInfo(hMonitor, &miex))
return size;
DEVMODE dm;
dm.dmSize = sizeof(dm);
dm.dmDriverExtra = 0;
//ENUM_CURRENT_SETTINGS 检索显示设备的当前设置
//ENUM_REGISTRY_SETTINGS 检索当前存储在注册表中的显示设备的设置
if (!EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm))
return size;
size.cx = dm.dmPelsWidth;
size.cy = dm.dmPelsHeight;
return size;
}
获取处理后显示屏的尺寸
//获取设备屏幕大小
QRect screenRect = screen->geometry();
完整的计算流程
//获取当前屏幕的信息
SIZE screenSize = GetScreenResolution((HWND)this->winId());
QScreen* screen = this->screen();
//获取设备屏幕大小
QRect screenRect = screen->geometry();
//计算DPI缩放比
double dpiScaleX = (double)screenSize.cx / (double)screenRect.width();
double dpiScaleY = (double)screenSize.cy / (double)screenRect.height();
我用我一个150%比例,一个100%比例的显示屏测了一下,能够正确输出
用150%缩放比例的显示屏测试
OK,获取的部分到此就完毕了
那接下来我们应当如何把这个比值代入计算呢?这里我通过记录鼠标在不同窗体的四个角点的坐标,发现这个计算可能还需要代入窗体坐标才能完成,也就是我们软件的左上角那个点。
目前我们需要计算多屏下的相对坐标,需要用到以下几个玩意:
1.全局绝对坐标,这个参数在多屏下,x能够到6000、7000的量级
2.窗体绝对坐标,QT默认获取即可,这个坐标会经过DPI缩放比计算的
3.DPI缩放比,核心参数
接下来我贴出我的计算流程。
//获取当前屏幕的信息
SIZE screenSize = GetScreenResolution((HWND)this->winId());
QScreen* screen = this->screen();
//获取设备屏幕大小
QRect screenRect = screen->geometry();
double dpiScaleX = (double)screenSize.cx / (double)screenRect.width();
double dpiScaleY = (double)screenSize.cy / (double)screenRect.height();
//计算屏幕坐标
long globX = GET_X_LPARAM(msg->lParam);
long globY = GET_Y_LPARAM(msg->lParam);
//获取当前的窗体坐标
QPoint wPos = QPoint(this->geometry().x(),this->geometry().y());
/// \bug 自带的这个mapFromGlobal在多屏中有巨大问题,计算不正确
//QPoint pos = mapFromGlobal(QPoint(x, y));
//手工计算相对坐标,带入屏幕缩放比
QPoint pos = QPoint(globX/dpiScaleX - wPos.x(),globY/dpiScaleY - wPos.y());
我用这个方法来测试
//把这个丢事件监视器里
QWidget *child = targetWidget->childAt(pos);
//判断是不是有效子控件,是的话打印名称
if (child) {
qDebug() << child->objectName();
}
测试发现完全正确!能够正确识别子控件的边缘。
然后完善一下我们的拉伸功能,我这里贴出完整的代码
bool FramelessWgt::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
if (eventType == "windows_generic_MSG") {
MSG *msg = static_cast<MSG *>(message);
if (msg->message == WM_NCCALCSIZE) {
*result = 0;
return true;
} else if (msg->message == WM_NCHITTEST) {
//计算屏幕坐标
long globX = GET_X_LPARAM(msg->lParam);
long globY = GET_Y_LPARAM(msg->lParam);
QPoint wPos = QPoint(this->geometry().x(),this->geometry().y());
//获取当前屏幕的信息
SIZE screenSize = GetScreenResolution((HWND)this->winId());
QScreen* screen = this->screen();
//获取设备屏幕大小
QRect screenRect = screen->geometry();
double dpiScaleX = (double)screenSize.cx / (double)screenRect.width();
double dpiScaleY = (double)screenSize.cy / (double)screenRect.height();
/// \bug 自带的这个mapFromGlobal在多屏中有巨大问题,计算不正确
//QPoint pos = mapFromGlobal(QPoint(x, y));
//手工计算相对坐标,带入屏幕缩放比
QPoint pos = QPoint(globX/dpiScaleX - wPos.x(),globY/dpiScaleY - wPos.y());
//判断当前鼠标区域
bool left = (globX/dpiScaleX) - wPos.x() < padding;
bool right = pos.x() > (width() * dpiScaleX - padding);
bool top = (globY/dpiScaleY) - wPos.y() < padding;
bool bottom = pos.y() > (height() * dpiScaleY - padding) ;
//鼠标移动到边缘时发出消息
*result = 0;
if (resizeEnable) {
if (left && top) {
*result = HTTOPLEFT;
} else if (left && bottom) {
*result = HTBOTTOMLEFT;
} else if (right && top) {
*result = HTTOPRIGHT;
} else if (right && bottom) {
*result = HTBOTTOMRIGHT;
} else if (left) {
*result = HTLEFT;
} else if (right) {
*result = HTRIGHT;
} else if (top) {
*result = HTTOP;
} else if (bottom) {
*result = HTBOTTOM;
}
}
//处理掉拉伸
if (0 != *result) {
return true;
}
}
}
}
搞定!