QT5 获取DPI缩放比以及多屏适配问题的记录

近期因为工作设备的高分辨率,导致软件在运行时控件占比和正常的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;
            }
        }
    }
}

搞定!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt是一个跨平台的GUI库,常用于开发桌面应用程序。在多屏环境下,Qt可以很好地支持屏幕缩放比例。 对于多屏环境,Qt提供了一种简单的方法来访问所有屏幕,并且多个屏幕可以在Qt中作为一个具有单一逻辑大小的桌面表现。Qt 5中提供了两种方法来实现屏幕缩放比例DPI缩放和UI缩放DPI缩放是指Qt通过重新计算每个屏幕上的像素密度来适应不同的屏幕,从而使应用程序在多屏幕上保持屏幕分辨率一致。UI缩放是指Qt可以通过缩放UI控件来适应不同的屏幕分辨率,这样就可以缩放应用程序的整个UI界面。 无论是DPI缩放还是UI缩放,都需要对应用程序进行一些调整,并且需要考虑到每个屏幕的分辨率和大小。Qt提供了一些函数和类来优化多屏环境下的应用程序,如QScreen和QWindow等。 综上所述,Qt提供了多种方法来适应多屏幕环境,其中包括DPI缩放和UI缩放开发人员应该根据具体情况选择合适的缩放方法,并且对应用程序进行必要的调整,以确保在多屏幕环境下有更好的用户体验。 ### 回答2: 在使用Qt多屏开发时,我们可能需要考虑到不同的屏幕分辨率和缩放比例对UI界面的影响,以保证程序在不同设备上具有良好的用户体验。 首先,Qt提供了一些关于屏幕分辨率和缩放比例的接口函数,比如QScreen::availableSize()和QScreen::logicalDotsPerInch()。这些函数可以帮助我们获取当前屏幕的大小和密度信息。同时,Qt还提供了一些函数来响应屏幕缩放比例的变化,比如QWindow::devicePixelRatioChanged()和QWidget::resizeEvent()。 其次,为了适应不同的分辨率和缩放比例,我们需要采取一些措施来调整UI界面的布局和大小。例如,我们可以使用Qt的布局管理器来自动计算和调整界面控件的大小和位置,以适应不同的屏幕分辨率。另外,我们还可以考虑使用矢量图形代替位图图像,以保证图像在不同分辨率下的清晰度。 最后,我们需要进行充分的测试和调试,以确保程序在不同屏幕分辨率和缩放比例下的表现都符合预期。这个过程既可以手动进行,也可以借助一些自动化测试工具来完成。 综上所述,Qt多屏开发需要考虑到不同的屏幕分辨率和缩放比例,采取相应的措施来调整UI界面的布局和大小,并进行充分的测试和调试。 ### 回答3: Qt 是一个跨平台的 C++ 应用程序开发框架,旨在为开发者提供高效且易于使用的工具来创建现代 UI 界面和应用程序。在 Qt 中,多屏和屏幕缩放比例都是需要注意的重要问题Qt 通过 QScreen 类提供了多屏支持。通过检索当前系统中的所有屏幕,并使用 QDesktopWidget 类的基本辅助函数计算其大小和位置,您可以获取要素,如应用程序是否覆盖多个屏幕等。在QScreen中,可以通过geometry()和availableGeometry() 函数获取屏幕的宽高和可用宽高。 对于屏幕缩放比例Qt 允许您在不同的设备上为不同的密度设置缩放比例DPI (Dots Per Inch) 是测量显示器分辨率的单位,通常用于确定屏幕上图像和文本的大小。在 Qt 中,可以使用QScreen::devicePixelRatio()获取屏幕的缩放比例。如果您想将您的应用程序设计为在不同分辨率和设备上都能正常显示,您需要根据屏幕的实际 DPI 设置计算缩放比例。在 Qt 中,可以使用QApplication::primaryScreen()->logicalDotsPerInch()获取当前屏幕的 DPI 值,然后根据需要采用不同的缩放比例来设置应用程序。 在 Qt 中,多屏和屏幕缩放比例都需要仔细考虑,以确保您的应用程序在不同的设备和分辨率上都能正常显示,并为最终用户提供出色的体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值