一直觉得 Qt 在 Windows 系统上的默认字体不太好看,不过自己写程序时自己去指定字体也很方便,就没怎么在意。这几个月专门用 Qt 写了一些程序,发现这还真的是个问题,因为包括官方的 Qt Creator 在内,都没有开放给用户自定义默认字体的设置,天天看着实在不怎么舒服。本来 Windows 系统是允许用户自定义桌面字体的,Win10 不知是出于去桌面化还是什么考虑,把这个功能又拿掉了。

当然,通过修改注册表还是可以修改系统默认字体的,而且我知道确实有这样的第三方工具。不过鉴于 Win10 对桌面系统日益后妈化的现实,这个接口说不定哪天也会被关掉,所以我个人并不怎么希望走这个路子。然而我也明白这确实是当前最简单、也不需要任何编程的手段。如果读者希望用这个方法的话,请自行搜索类似 Font Changer 之类的关键字,下面的文字就不需要再看了。用编程的方法则比较麻烦,需要自行修改一些代码,愿意自己动手的朋友请继续阅读。

要解决该问题,首先请阅读 QTBUG-58610 ,我也是找资料时偶然发现这条信息的。按照该 bug 的描述,该问题的基本原因在于 Qt 在获取字体时使用了教早的 Win32 函数 GetStockObject ,而较新的系统中应该使用 SystemParametersInfo 。看起来只是修改一个系统调用,似乎不难解决,但审核记录却显示修正会放到下一个主要版本(6.0)。这意味着即将到来的 LTS 版本(5.12)不会解决该问题。或许是出于审慎和兼容性考虑吧,不过我对这个结果是有点失望的。好在 Qt 是开源的,并且问题看起来也很简单,我决心自己看一下能不能自己修改源码来启用新的字体。

我使用的是当前 Qt 的最新正式版 5.11.2。首先声明,本文描述的方法是我自行尝试的,并未得到官方验证,虽然我自己已经作了测试,并分析过可能受影响的相关代码,自信还是比较可靠的,但并不保证 100% 没有问题,同时也仅在 Windows 做了测试,所以内容仅供参考,对 Qt 有自行编译经验的朋友不妨尝试。

简单查找了一下,对上述接口的调用主要在如下位置: qtbase\src\platformsupport\fontdatabases\windows\qwindowsfontdatabase.cpp ,文件中又有两处稍有不同的实现,首先是 QWindowsFontDatabase::systemFont :

// ### fixme Qt 6 (QTBUG-58610): See comment at QWindowsFontDatabase::systemDefaultFont()
HFONT QWindowsFontDatabase::systemFont()
{static const HFONT stock_sysfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);return stock_sysfont;
}

看起来 程序员 已经标记出问题,但并未修改。注释告诉我们还要注意 QWindowsFontDatabase::systemDefaultFont() ,那接下来就看看这个函数:

QFont QWindowsFontDatabase::systemDefaultFont()
{
#if QT_VERSION >= 0x060000// Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610)NONCLIENTMETRICS ncm;ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT);SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0);const QFont systemFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
#elseLOGFONT lf;GetObject(QWindowsFontDatabase::systemFont(), sizeof(lf), &lf);QFont systemFont =  QWindowsFontDatabase::LOGFONT_to_QFont(lf);// "MS Shell Dlg 2" is the correct system font >= Win2kif (systemFont.family() == QLatin1String("MS Shell Dlg"))systemFont.setFamily(QStringLiteral("MS Shell Dlg 2"));// Qt 5 by (Qt 4) legacy uses GetStockObject(DEFAULT_GUI_FONT) to// obtain the default GUI font (typically "MS Shell Dlg 2, 8pt"). This has been// long deprecated; the message font of the NONCLIENTMETRICS structure obtained by// SystemParametersInfo(SPI_GETNONCLIENTMETRICS) should be used instead (see// QWindowsTheme::refreshFonts(), typically "Segoe UI, 9pt"), which is larger.
#endif // Qt 5qCDebug(lcQpaFonts) << __FUNCTION__ << systemFont;return systemFont;
}

嗯,修改代码已经给出,只是标记为从 V6 开始启用。要启用的话,这里只要切换到新代码即可,但上面的 systemFont 呢?是简单的废弃掉了,还是有别处代码继续调用?

好在 HFONT 是 Windows 特定类型,考虑到代码结构,该类应该只是在内部作为平台实现,不太可能由其他代码直接调用,因此我们可以把查找范围限定在 platformsupport 内部,避免搜索整个 Qt 库。搜索一下可知,除了上面已经看到的 systemDefaultFont() 之外,调用 systemFont() 的尚有三处。仔细观察代码可知,这些调用主要为另一个名为 QWindowsFontEngine 的类提供字体。

这就有点麻烦了。熟悉 Win32 API 的朋友应该知道, GetStockObject 返回的对象,不论是否调用 DeleteObject 都不会有不良后果,所以你可以放心大胆的调用而无需担心 GDI 资源泄露。而 SystemParametersInfo 就不同了,自己创建的字体,必须保证在正确的时间释放,否则要么丢失字体,要么泄露句柄。为保证修改安全,我们不能只考虑前面两处地方,而必须考虑到调用者是否、以及应该在何处释放资源的问题。

qwindowsfontdatabase.cpp 中引用到 sytsemFont() 方法的代码有两处。一处就在前面的 systemDefaultFont() 调用中,不过我们已经看到了,如果切换到 V6 的实现,那么该调用将不会再起作用,我们可以安全地忽略它。另一处在 createEngine() 方法中:

QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request, const QString &faceName,int dpi,const QSharedPointer<QWindowsFontEngineData> &data)
{...if (request.stretch != 100) {HFONT hfont = CreateFontIndirect(&lf);if (!hfont) {qErrnoWarning("%s: CreateFontIndirect failed", __FUNCTION__);hfont = QWindowsFontDatabase::systemFont();}HGDIOBJ oldObj = SelectObject(data->hdc, hfont);TEXTMETRIC tm;if (!GetTextMetrics(data->hdc, &tm))qErrnoWarning("%s: GetTextMetrics failed", __FUNCTION__);elself.lfWidth = tm.tmAveCharWidth * request.stretch / 100;SelectObject(data->hdc, oldObj);DeleteObject(hfont);}...

上述代码会在处理完毕后调用 DeleteObject 。很好,这段代码是安全的,我们不用管它了。接下来看 QWindowsFontEngine 中的调用。

首先看 qwindowsfontengine.cpp 中的调用点:对 systemFont() 的调用的两处分别在 QWindowsFontEngine 的构造函数和析构函数。这是一个好现象,说明生命周期非常明确,但我们仍然要了解该调用生成的对象是如何管理的。首先看构造函数:

QWindowsFontEngine::QWindowsFontEngine(const QString &name,LOGFONT lf,const QSharedPointer<QWindowsFontEngineData> &fontEngineData): QFontEngine(Win),m_fontEngineData(fontEngineData),_name(name),m_logfont(lf),ttf(0),hasOutline(0)
{qCDebug(lcQpaFonts) << __FUNCTION__ << name << lf.lfHeight;hfont = CreateFontIndirect(&m_logfont);if (!hfont) {qErrnoWarning("%s: CreateFontIndirect failed for family '%s'", __FUNCTION__, qPrintable(name));hfont = QWindowsFontDatabase::systemFont();}HDC hdc = m_fontEngineData->hdc;SelectObject(hdc, hfont);...

可见, QWindowsFontEngine 是将 systemFont() 作为一种后备机制,只有 CreateFontIndirect 不成功的情况下才会调用它。至于首选字体是什么可以不用关心它,但这样字体的来源有两种可能,这给我们判断是否应该删除增加了一点困难。再看析构函数:

QWindowsFontEngine::~QWindowsFontEngine()
{if (designAdvances)free(designAdvances);if (widthCache)free(widthCache);// make sure we aren't by accident still selectedSelectObject(m_fontEngineData->hdc, QWindowsFontDatabase::systemFont());if (!DeleteObject(hfont))qErrnoWarning("%s: QFontEngineWin: failed to delete font...", __FUNCTION__);qCDebug(lcQpaFonts) << __FUNCTION__ << _name;...
}

这里的处理逻辑有点绕,因为 hfont 本来就可能来自 systemFont() ,结果在释放时再一次选择了该字体。这种处理原来是没有问题的(因为 GetStockObject 返回的对象不需要删除),但对新字体的实现明显就会有泄露的风险了。我们看到,析构函数后面会调用 DeleteObject ,所以不论构造函数是如何生成字体的,这里确实会释放,不必担心。那么问题就在于上面的 SelectObject 怎么办。思考一番后,我决定这样:把这样的 systemFont() 改成原来的实现( GetStockObject ) ,这样就无需担心泄露了。鉴于 QWindowsFontEngineData 仅在 QWindowsFontEngine 内部使用,其 hdc 在释放以后应该不会再用于 Font Engine,所以这样做应该是安全的。

方法已经考虑清楚,接下来就是实现了。首先找到 QWindowsFontDatabase::systemDefaultFont() ,强行开启 V6 分支判断:

QFont QWindowsFontDatabase::systemDefaultFont()
{
//{{HACK_BEGIN
#if 1 // QT_VERSION >= 0x060000
//}}HACK_END

上述代码是我自己的习惯,便于以后查找修改的地方,因为 Qt 代码实在是太庞大了,为一点小小的修改就上版本控制的话会非常慢。当然修改之前把原来的代码备份一次是个好习惯。

然后修改 QWindowFontDatabase::systemFont() ,使用新的字体查找方法:

// ### fixme Qt 6 (QTBUG-58610): See comment at QWindowsFontDatabase::systemDefaultFont()
HFONT QWindowsFontDatabase::systemFont()
{
//{{HACK_BEGIN
//    static const HFONT stock_sysfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
//    return stock_sysfont;NONCLIENTMETRICS ncm;ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT);SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0);return CreateFontIndirect(&ncm.lfMessageFont);
//}}HACK_END
}

这里和原来的处理有一点差别。原来的代码将字体作为静态变量,估计是希望优化性能吧。但看过对应的实现,我们知道调用方通常会在结束之后调用 DeleteObject() ,所以现在用静态变量是不太现实的。对当代计算机来说 Font 对象只要不泄露,多调用几次应该不会造成明显的性能问题。

然后是 qwindowsfontengine.cpp 。我们只需要替换析构函数中的实现就可以了:

QWindowsFontEngine::~QWindowsFontEngine()
{...// make sure we aren't by accident still selected
//{{HACK_BEGIN  // SelectObject(m_fontEngineData->hdc, QWindowsFontDatabase::systemFont());SelectObject(m_fontEngineData->hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
//}}HACK_END

程序到此修改完毕,我们可以重新编译 Qt, 然后走开去做点别的(需要很长时间)。

编译完成后,你可以用生成的 DLL 替换原来的版本,即可生效。其实严格说来,我们所作的只是一个很小的修改,单独复制下列文件即可: plugins/platforms/qwindows.dll (调试版本为 qwindowsd.dll )。

我们看看修改后的效果。我保留了原版和修改过的版本,这样放在一起容易看出差别。可以看出,新版的字体比原来更加丰满圆润,我个人觉得顺眼多了。你觉得呢?

无关的吐槽:就在写作本文时,Visual Studio 2017 更新 15.8.7 再次搞坏了我的 Qt 构建,本来 15.8.6 还是完全没有问题的......我以为 VS2017 经过这么多更新应该已经很稳定了,没想到还是和 Win10 一个尿性。好吧,用回 VS2015,至少不会给我搞什么幺蛾子。

本文来源:码农网
本文链接:https://www.codercto.com/a/36420.html

Qt 在 Windows 下默认字体比较丑,但是我们有办法修改它相关推荐

  1. linux添加windows字体文件夹里,如何把Windows下的字体添加到Linux中

    如何把Windows下的字体添加到Linux中 Linux(Fedora/Ubuntu/CentOS)的字体实在不尽如人意,而且在网页及文档显示时很多字无法显示出来,特别多的空白和乱码,其实,我们可以 ...

  2. linux系统字体放在哪,可以把windows下的字体安装到Linux系统下吗

    Linux系统虽然强大,但是很多用户都不习惯里面的字体,怎么办呢?其实,我们可以把windows下的字体和自己心仪的字体添加到Linux中,本文将介绍如何在Linux下添加字体. Linux添加字体方 ...

  3. 如何把Windows下的字体添加到Linux中

    如何把Windows下的字体添加到Linux中 Linux(Fedora/Ubuntu/CentOS)的字体实在不尽如人意,而且在网页及文档显示时很多字无法显示出来,特别多的空白和乱码,其实,我们可以 ...

  4. Windows 下 Quartus 检测不到 USB-Blaster 终极解决办法

    Windows 下 Quartus 检测不到 USB-Blaster 终极解决办法 转自https://blog.csdn.net/acang301/article/details/50471067? ...

  5. Qt在Windows下的三种编程环境搭建

    未经验证,记录在此. 尊重作者,支持原创,如需转载,请附上原地址:http://blog.csdn.net/libaineu2004/article/details/17363165 从QT官网可以得 ...

  6. Linux下默认字体是什么,linux默认字体是什么

    呵呵,都没看出差异,难道我眼睛有问题?字体不同?楼主考眼力吧?###### 引用来自#2楼"xyz555"的帖子 呵呵,都没看出差异,难道我眼睛有问题?字体不同?楼主考眼力吧? 不 ...

  7. QT在Windows下检测USB设备热拔插的思路

    一.问题描述: 使用QT开发视频会议时需要实现实时检测USB摄像头/麦克风拔插的功能,这里主要涉及到对一些Windows API的了解以及windows系统的设备管理识别不同种设备时的原理,在实现过程 ...

  8. 使用 qt 在 windows 下开发云盘软件

    在 Windows 下使用 Qt 开发云盘软件需要您先安装 Qt 开发环境,然后进行如下步骤: 创建一个新的 Qt 工程. 在工程中添加必要的界面元素,包括登录界面.文件列表界面.上传/下载文件界面等 ...

  9. Windows下查看端口被占用问题和解决办法

    在运行程序的时候,我们经常会遇到端口被占用的问题,那么在Windows下我们咋查看端口被占用尼? 在这里之前,我们搞清楚一个问题,什么叫端口被占用? 简单的说,就是一个进程PID使用两个端口号,举例说 ...

最新文章

  1. CSDN公众号新功能上线,居然还能搜出小姐姐???
  2. iscsi lun extend---windows客户端
  3. 第三章 处理机调度与死锁
  4. 支持javascript的ppt软件_github重磅推荐!一个很好用的PPT生成工具
  5. C++Opengl三维列表堆罗汉源码
  6. 8086汇编-做1到100的加法并显示结果
  7. 如何理解Java的类变量、成员变量、常量、类属性、实例属性、字段(field)、成员方法、类方法
  8. rxjs为什么用的人少_工伤为什么公司不怕打官司
  9. sql进程意外终止_字节跳动五面都过了,竟然意外被刷了下来,问了hr原因竟说是。。。。。...
  10. matlab怎么计算行列式,matlab一元线性回归方程的计算和检验/用四种方法计算行列式...
  11. c 压缩java解压文件,java 压缩文件 解压缩文件
  12. treetable怎么带参数_jquery treeTable插件使用细则
  13. 搭建家庭 NAS 服务器有什么好方案?
  14. Linux操作系统加固
  15. CI/DI持续集成部署
  16. 小程序客服功能可以实现哪些操作?
  17. 【hive】hive如何将Jan 1, 2021 12:40:46 PM时间格式转换为指定格式
  18. 【Hibernate步步为营】--映射合集汇总
  19. apM Coin与Block72达成战略合作携手加速全球扩张
  20. 华擎 FM2A75 PRO4刷Bios历险

热门文章

  1. 渲染有问题?怎么办?6种方法让你渲染无忧
  2. python中List类型与numpy.array类型的互相转换
  3. 南北朝时期中国茶业概况
  4. 【实战问题】【3】iPhone无法播放video标签中的视频
  5. 数字签名工具 linux,制作只属于自己的数字签名在线工具与控件木马
  6. spring helloWord
  7. 使用js设置快捷键操作页面,js热键控制页面操作
  8. 大有SAP,中有NetSuite,小有Intuit,为啥?
  9. Electron教程3_使用VSCode来调试Electron
  10. 计算机对等网络的组建,两台电脑对等网组建连接教程