前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Qt源码笔记】简要说说 Qt5 中的 HighDPI 支持

【Qt源码笔记】简要说说 Qt5 中的 HighDPI 支持

作者头像
Harper
发布2021-07-27 10:10:13
2.4K0
发布2021-07-27 10:10:13
举报
文章被收录于专栏:Harper的碎碎念

想起之前在公司做的关于 HighDPI 的适配,在 Qt4 下可以说是比较繁琐,代码敲到手疼。早就听说 Qt5.6 开始支持了 HighDPI ,一直没机会看详细的代码。一直到开始做 Gal ,才刚好在 Qt5 下需要 HighDPI 支持。用过之后,真的感叹,用起来太方便了。故看了一下详细实现。不过比较遗憾的是代码中有一个小瑕疵。

使用

其实想得到 Qt 给予的 HighDPI 支持,是非常之简单。只要在 QApplication 构造之前,开启 Qt::AA_EnableHighDpiScaling 这个属性。其实在代码中使用这个属性,等于环境中开启 QT_AUTO_SCREEN_SCALE_FACTOR 环境变量。还有另外的环境变量支持其他的 HighDPI 功能。这个参考文档即可

这里有一个小 tip :HighDPI 只是是根据显示器的像素密度来调整大小。在 Qt 中,用过 QFont 的人都会知道。QFont 中有两个方法:setPixelSizesetPointSize很多人对此不是很明白,为什么要设置这两个方法。这里便可以找到答案。设置字体的Pixel Size,则会根据显示器的像素密度去改变字体大小;而设置字体的Point Size则不会更改,因为Point Size是基于显示器的物理单元。

关于 HighDPI ,一个比较良好的代码习惯,其实在 Qt 的 HighDPI 文档部分中有提到:

  • Always use the qreal versions of the QPainter drawing API.
  • Size windows and dialogs in relation to the screen size.
  • Replace hard-coded sizes in layouts and drawing code by values calculated from font metrics or screen size.

总而言之,使用的时候只要一个开关即可开启 HighDPI 支持,这一点让我还是十分好奇的。迫不及待地翻看了源码。

代码实现

其实关于 HighDPI 的代码,基本就在两部分中。

其中一部分在 qtbase\src\gui\kernel 目录下 qhighdpiscaling_p.hqhighdpiscaling.cpp这两个文件中的 QHighDpiScaling 类里。

代码语言:javascript
复制
class Q_GUI_EXPORT QHighDpiScaling {
public:
    static void initHighDpiScaling();
    static void updateHighDpiScaling();
    static void setGlobalFactor(qreal factor);
    static void setScreenFactor(QScreen *window, qreal factor);
    static bool isActive() { return m_active; }
    static qreal factor(const QWindow *window);
    static qreal factor(const QScreen *screen);
    static qreal factor(const QPlatformScreen *platformScreen);
    static QPoint origin(const QScreen *screen);
    static QPoint origin(const QPlatformScreen *platformScreen);
    static QPoint mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen);
    static QPoint mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen);
    static QDpi logicalDpi();
private:
    static qreal screenSubfactor(const QPlatformScreen *screen);
    static qreal m_factor;
    static bool m_active;
    static bool m_usePixelDensity;
    static bool m_globalScalingActive;
    static bool m_pixelDensityScalingActive;
    static bool m_screenFactorSet;
    static QDpi m_logicalDpi;
};

这个类最大的特色可以说就是纯静态的了。不过按照逻辑来说,也是合理的。其中 initHighDpiScaling()updateHighDpiScaling()可以说是两个比较重要的方法了,这两个方法掌管着整个 HighDPI 支持的命脉。其实里边的内容只是一些方法的简单包装。只看堆栈调用的话:

代码语言:javascript
复制
>   Qt5Guid.dll!QHighDpiScaling::initHighDpiScaling() 行 254	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::createPlatformIntegration() 行 1301	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::createEventDispatcher() 行 1403	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::createEventDispatcher() 行 187	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::init() 行 859	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::init() 行 1431	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::init() 行 569	C++
 	Qt5Widgetsd.dll!QApplication::QApplication(int & argc, char * * argv, int _internal) 行 556	C++

可以看出,在 QApplication 构造的时候,会走入 HighDPI 的相关逻辑,这也是文档中要求要在构造之前开启开关是一致的,因为构造的时候就要检查这个属性的状态。

实际计算缩放因子的方法,应该是这个:

代码语言:javascript
复制
qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)

逻辑也是十分简单了。不用做过多解释。不过这里边有一个pixelDensity()的调用,内容还挺有意思的。

代码语言:javascript
复制
qreal QWindowsScreen::pixelDensity() const
{
    // QTBUG-49195: Use logical DPI instead of physical DPI to calculate
    // the pixel density since it is reflects the Windows UI scaling.
    // High DPI auto scaling should be disabled when the user chooses
    // small fonts on a High DPI monitor, resulting in lower logical DPI.
    return qMax(1, qRound(logicalDpi().first / 96));
}

这里边的逻辑可以明显地看到,当我们在 Windows 系统下使用类似 125% 的缩放比例的时候,这里边计算到的缩放比例还是 1。然后去 Qt BugReport 看了一下。QTBUG-70721 就是这个问题。

上边说到,代码实现有两部分,另外一部分则是在 qtbase\src\widgets\styles 目录下的qstylehelper_p.hqstylehelper.cpp中的QStyleHelper命名空间中。

代码语言:javascript
复制
qreal dpiScaled(qreal value)
{
    // On mac the DPI is always 72 so we should not scale it
    return value;
    static const qreal scale = qreal(qt_defaultDpiX()) / 96.0;
    return value * scale;
}

如果在 Qt4 下有做过 HighDPI 的相关逻辑,想必对这个方法是不陌生的。至此基本上 Qt HighDPI 支持的代码逻辑基本找全。

小瑕疵

上边我提到过代码中的小瑕疵。就在上边那段代码上。不难看出这个scale是一个函数中的静态变量,后续对这个函数再次调用已经不改变scale的值了。

看到这里会觉得,大概是个隐患,然后再来看qt_defaultDpiX()这个方法:(这个方法在 qtbase\src\gui\text 目录的qfont.cpp文件中)

代码语言:javascript
复制
Q_GUI_EXPORT int qt_defaultDpiX()
{
    if (QCoreApplication::instance()->testAttribute(Qt::AA_Use96Dpi))
        return 96;
    if (!qt_is_gui_used)
        return 75;
    if (const QScreen *screen = QGuiApplication::primaryScreen())
        return qRound(screen->logicalDotsPerInchX());
    //PI has not been initialised, or it is being initialised. Give a default dpi
    return 100;
}

看到这里也就只有第三个 if 会导致这个方法的返回值不确定。

那很自然的就会想到,如果当 dpiScaled 调用的时候第三个 if 不起作用,那将是可怕的结果。所以紧接着探究这个 screen 。这部分过程略过,直接说结论。screen 能正常取到的前提是 QGuiApplicationPrivate::screen_list 这个列表是有内容的。而这个列表第一次被添加的时机堆栈:

代码语言:javascript
复制
>	Qt5Guid.dll!QPlatformIntegration::screenAdded(QPlatformScreen * ps, bool isPrimary) 行 478	C++
 	qwindowsd.dll!QWindowsIntegration::emitScreenAdded(QPlatformScreen * s, bool isPrimary) 行 110	C++
 	qwindowsd.dll!QWindowsScreenManager::handleScreenChanges() 行 546	C++
 	qwindowsd.dll!QWindowsIntegration::QWindowsIntegration(const QStringList & paramList) 行 276	C++
 	qwindowsd.dll!QWindowsGdiIntegration::QWindowsGdiIntegration(const QStringList & paramList) 行 59	C++
 	qwindowsd.dll!QWindowsIntegrationPlugin::create(const QString & system, const QStringList & paramList, int & __formal, char * * __formal) 行 114	C++
 	Qt5Guid.dll!qLoadPlugin<QPlatformIntegration,QPlatformIntegrationPlugin,QStringList const & __ptr64,int & __ptr64,char * __ptr64 * __ptr64 & __ptr64>(const QFactoryLoader * loader, const QString & key, const QStringList & <args_0>, int & <args_1>, char * * & <args_2>) 行 108	C++
 	Qt5Guid.dll!QPlatformIntegrationFactory::create(const QString & platform, const QStringList & paramList, int & argc, char * * argv, const QString & platformPluginPath) 行 72	C++
 	Qt5Guid.dll!init_platform(const QString & pluginNamesWithArguments, const QString & platformPluginPath, const QString & platformThemeName, int & argc, char * * argv) 行 1179	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::createPlatformIntegration() 行 1383	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::createEventDispatcher() 行 1403	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::createEventDispatcher() 行 187	C++
 	Qt5Cored.dll!QCoreApplicationPrivate::init() 行 859	C++
 	Qt5Guid.dll!QGuiApplicationPrivate::init() 行 1431	C++
 	Qt5Widgetsd.dll!QApplicationPrivate::init() 行 569	C++
 	Qt5Widgetsd.dll!QApplication::QApplication(int & argc, char * * argv, int _internal) 行 556	C++

从这里可以看到,是在 QApplication 构造的时候。

所以可以得出一个结论,当在QApplication构造的之前调用QStyleHelper::dpiScaled得到的结果则可能不是准确的,也会导致,在以后得到结果都是错误的。没有经验的人也许会觉得在QApplication构造之前调用这个是没意义的,所以认为这个调用并不常见。此处我举一例以供参考。

很多人习惯提前定义一些比较固定的量,在某个 cpp 中,也许我们能看到这样一种代码,它有可能是直接写成,也有可能在实现 HighDPI 过程中更改而成

代码语言:javascript
复制
namespace
{
    qreal testa_width = QStyleHelper::dpiScaled(1);
}
static qreal testb_width = QStyleHelper::dpiScaled(1);

如果在代码的上方出现了这种,则它们就属于是一种比较可怕的代码,可以影响全局调用dpiScaled得不到正确结果。

总结

不过即使有一点点小瑕疵,但是 Qt 对 HighDPI 的实现,以及调用设计还是有很多值得借鉴之处的。本文也只是对 Qt HighDPI 支持比较简要的分析,还有很多细节,限于篇幅,并没有展开来说……

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用
  • 代码实现
      • 小瑕疵
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档