Qt 在 Windows 下默认字体比较丑,但是我们有办法修改它
一直觉得 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 下默认字体比较丑,但是我们有办法修改它相关推荐
- linux添加windows字体文件夹里,如何把Windows下的字体添加到Linux中
如何把Windows下的字体添加到Linux中 Linux(Fedora/Ubuntu/CentOS)的字体实在不尽如人意,而且在网页及文档显示时很多字无法显示出来,特别多的空白和乱码,其实,我们可以 ...
- linux系统字体放在哪,可以把windows下的字体安装到Linux系统下吗
Linux系统虽然强大,但是很多用户都不习惯里面的字体,怎么办呢?其实,我们可以把windows下的字体和自己心仪的字体添加到Linux中,本文将介绍如何在Linux下添加字体. Linux添加字体方 ...
- 如何把Windows下的字体添加到Linux中
如何把Windows下的字体添加到Linux中 Linux(Fedora/Ubuntu/CentOS)的字体实在不尽如人意,而且在网页及文档显示时很多字无法显示出来,特别多的空白和乱码,其实,我们可以 ...
- Windows 下 Quartus 检测不到 USB-Blaster 终极解决办法
Windows 下 Quartus 检测不到 USB-Blaster 终极解决办法 转自https://blog.csdn.net/acang301/article/details/50471067? ...
- Qt在Windows下的三种编程环境搭建
未经验证,记录在此. 尊重作者,支持原创,如需转载,请附上原地址:http://blog.csdn.net/libaineu2004/article/details/17363165 从QT官网可以得 ...
- Linux下默认字体是什么,linux默认字体是什么
呵呵,都没看出差异,难道我眼睛有问题?字体不同?楼主考眼力吧?###### 引用来自#2楼"xyz555"的帖子 呵呵,都没看出差异,难道我眼睛有问题?字体不同?楼主考眼力吧? 不 ...
- QT在Windows下检测USB设备热拔插的思路
一.问题描述: 使用QT开发视频会议时需要实现实时检测USB摄像头/麦克风拔插的功能,这里主要涉及到对一些Windows API的了解以及windows系统的设备管理识别不同种设备时的原理,在实现过程 ...
- 使用 qt 在 windows 下开发云盘软件
在 Windows 下使用 Qt 开发云盘软件需要您先安装 Qt 开发环境,然后进行如下步骤: 创建一个新的 Qt 工程. 在工程中添加必要的界面元素,包括登录界面.文件列表界面.上传/下载文件界面等 ...
- Windows下查看端口被占用问题和解决办法
在运行程序的时候,我们经常会遇到端口被占用的问题,那么在Windows下我们咋查看端口被占用尼? 在这里之前,我们搞清楚一个问题,什么叫端口被占用? 简单的说,就是一个进程PID使用两个端口号,举例说 ...
最新文章
- CSDN公众号新功能上线,居然还能搜出小姐姐???
- iscsi lun extend---windows客户端
- 第三章 处理机调度与死锁
- 支持javascript的ppt软件_github重磅推荐!一个很好用的PPT生成工具
- C++Opengl三维列表堆罗汉源码
- 8086汇编-做1到100的加法并显示结果
- 如何理解Java的类变量、成员变量、常量、类属性、实例属性、字段(field)、成员方法、类方法
- rxjs为什么用的人少_工伤为什么公司不怕打官司
- sql进程意外终止_字节跳动五面都过了,竟然意外被刷了下来,问了hr原因竟说是。。。。。...
- matlab怎么计算行列式,matlab一元线性回归方程的计算和检验/用四种方法计算行列式...
- c 压缩java解压文件,java 压缩文件 解压缩文件
- treetable怎么带参数_jquery treeTable插件使用细则
- 搭建家庭 NAS 服务器有什么好方案?
- Linux操作系统加固
- CI/DI持续集成部署
- 小程序客服功能可以实现哪些操作?
- 【hive】hive如何将Jan 1, 2021 12:40:46 PM时间格式转换为指定格式
- 【Hibernate步步为营】--映射合集汇总
- apM Coin与Block72达成战略合作携手加速全球扩张
- 华擎 FM2A75 PRO4刷Bios历险