【写在前面】

在现代桌面应用程序开发中,系统主题感知是一项重要的功能,它使得应用程序能够根据用户的系统主题设置(如深色模式或浅色模式)自动调整其外观。

Qt 作为一个跨平台的C++图形用户界面应用程序开发框架,提供了丰富的工具和类来实现这一功能。


【正文开始】

一、使用效果

二、系统主题感知助手类(SystemThemeHelper)

SystemThemeHelper类是一个封装了系统主题感知功能的Qt对象。它主要通过读取系统设置和监听系统主题变化来更新应用程序的主题颜色和颜色方案。

  1. 类定义与属性

    在systemthemehelper.h中,SystemThemeHelper类继承自QObject,并定义了两个属性:themeColor和colorScheme。这两个属性分别表示当前的主题颜色和颜色方案(深色、浅色或无)。

    class SystemThemeHelper : public QObject
    {
    Q_OBJECT
    Q_PROPERTY(QColor themeColor READ themeColor NOTIFY themeColorChanged)
    Q_PROPERTY(SystemThemeHelper::ColorScheme colorScheme READ colorScheme NOTIFY colorSchemeChanged)
    // ...
    };

    ColorScheme是一个枚举类,定义了三种颜色方案:None、Dark和Light。

  2. 构造函数与析构函数

    SystemThemeHelper的构造函数初始化了一些私有成员变量,并启动了一个定时器,用于定期更新主题颜色和颜色方案。析构函数则负责清理资源。

    SystemThemeHelper::SystemThemeHelper(QObject *parent)
    : QObject{parent}, d_ptr(new SystemThemeHelperPrivate(this))
    {
    Q_D(SystemThemeHelper);
    d->m_themeColor = getThemeColor();
    d->m_colorScheme = getColorScheme();
    d->m_timer.start(200, this);
    #ifdef Q_OS_WIN
    initializeFunctionPointers();
    #endif
    } SystemThemeHelper::~SystemThemeHelper()
    {
    // 清理资源
    }
  3. 获取主题颜色和颜色方案

    getThemeColor和getColorScheme是两个不可用于绑定的方法,它们立即返回当前的主题颜色和颜色方案,但不会触发任何更新通知。这两个方法主要用于快速获取当前设置,而不关心后续的变化。

    QColor SystemThemeHelper::getThemeColor() const
    {
    Q_D(const SystemThemeHelper);
    #ifdef Q_OS_WIN
    return QColor::fromRgb(d->m_themeColorSettings.value("ColorizationColor").toUInt());
    #endif
    } SystemThemeHelper::ColorScheme SystemThemeHelper::getColorScheme() const
    {
    Q_D(const SystemThemeHelper);
    #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
    const auto scheme = QGuiApplication::styleHints()->colorScheme();
    return scheme == Qt::ColorScheme::Dark ? ColorScheme::Dark : ColorScheme::Light;
    #else
    #ifdef Q_OS_WIN
    return !d->m_colorSchemeSettings.value("AppsUseLightTheme").toBool() ? ColorScheme::Dark : ColorScheme::Light;
    #else //linux
    const QPalette defaultPalette;
    const auto text = defaultPalette.color(QPalette::WindowText);
    const auto window = defaultPalette.color(QPalette::Window);
    return text.lightness() > window.lightness() ? ColorScheme::Dark : ColorScheme::Light;
    #endif // Q_OS_WIN
    #endif // QT_VERSION
    }
  4. 更新主题颜色和颜色方案

    themeColor和colorScheme是两个可用于绑定的方法,它们返回当前的主题颜色和颜色方案,并在值发生变化时发出通知。这两个方法内部调用了私有成员函数的更新逻辑。

    QColor SystemThemeHelper::themeColor()
    {
    Q_D(SystemThemeHelper);
    d->_updateThemeColor();
    return d->m_themeColor;
    } SystemThemeHelper::ColorScheme SystemThemeHelper::colorScheme()
    {
    Q_D(SystemThemeHelper);
    d->_updateColorScheme();
    return d->m_colorScheme;
    }
  5. 设置窗口标题栏模式

    setWindowTitleBarMode方法允许设置窗口标题栏的模式(深色或浅色)。这个方法在Windows平台上通过调用 DWM API 实现,而在其他平台上则不支持。

    bool SystemThemeHelper::setWindowTitleBarMode(QWindow *window, bool isDark)
    {
    #ifdef Q_OS_WIN
    return bool(pDwmSetWindowAttribute ? !pDwmSetWindowAttribute(HWND(window->winId()), 20, &isDark, sizeof(BOOL)) : false);
    #else
    return false;
    #endif //Q_OS_WIN
    }
  6. 定时器事件处理

    timerEvent方法是一个虚函数,用于处理定时器事件。它定期调用更新函数来检查主题颜色和颜色方案是否发生变化,并在变化时发出通知。

    void SystemThemeHelper::timerEvent(QTimerEvent *)
    {
    Q_D(SystemThemeHelper);
    d->_updateThemeColor();
    d->_updateColorScheme();
    }

三、实现细节

SystemThemeHelperPrivate是SystemThemeHelper的私有实现类,它封装了所有的实现细节和状态变量。这个类主要负责读取系统设置、更新主题颜色和颜色方案,并发出通知。

  1. 构造函数与成员变量

    SystemThemeHelperPrivate的构造函数接收一个指向SystemThemeHelper的指针,并初始化成员变量。成员变量包括主题颜色、颜色方案、定时器和一些平台特定的设置对象。

    SystemThemeHelperPrivate::SystemThemeHelperPrivate(SystemThemeHelper *q)
    : q_ptr(q)
    {
    // 初始化成员变量
    }
  2. 更新函数

    _updateThemeColor和_updateColorScheme是两个更新函数,它们检查当前的主题颜色和颜色方案是否发生变化,并在变化时更新成员变量并发出通知。

    void SystemThemeHelperPrivate::_updateThemeColor()
    {
    Q_Q(SystemThemeHelper);
    auto nowThemeColor = q->getThemeColor();
    if (nowThemeColor != m_themeColor) {
    m_themeColor = nowThemeColor;
    emit q->themeColorChanged();
    }
    } void SystemThemeHelperPrivate::_updateColorScheme()
    {
    Q_Q(SystemThemeHelper);
    auto nowColorScheme = q->getColorScheme();
    if (nowColorScheme != m_colorScheme) {
    m_colorScheme = nowColorScheme;
    emit q->colorSchemeChanged();
    }
    }
  3. 平台特定的实现

    在Windows平台上,SystemThemeHelperPrivate使用QSettings来读取系统主题设置,并使用DWM API来设置窗口标题栏的模式。这些实现细节被封装在条件编译块中,以确保跨平台的兼容性。

    #ifdef Q_OS_WIN
    QSettings m_themeColorSettings{QSettings::UserScope, "Microsoft", "Windows\\DWM"};
    QSettings m_colorSchemeSettings{QSettings::UserScope, "Microsoft", "Windows\\CurrentVersion\\Themes\\Personalize"};
    static DwmSetWindowAttributeFunc pDwmSetWindowAttribute = nullptr;
    // ...
    static inline bool initializeFunctionPointers()
    {
    // 初始化DWM API函数指针
    }
    #endif //Q_OS_WIN

四、如何使用

C++:

	SystemThemeHelper *helper = new SystemThemeHelper;
QObject::connect(helper, &SystemThemeHelper::themeColorChanged, [helper]{
qDebug() << helper->getThemeColor();
});
QObject::connect(helper, &SystemThemeHelper::colorSchemeChanged, [helper]{
qDebug() << helper->getColorScheme();
});

Qml:

import QtQuick 2.15
import QtQuick.Window 2.15 import DelegateUI.Utils 1.0 Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("SystemThemeHelper Test - ") + (themeHelper.colorScheme == SystemThemeHelper.Dark ? "Dark" : "Light")
color: themeHelper.colorScheme == SystemThemeHelper.Dark ? "black" : "white" Behavior on color { ColorAnimation { } } SystemThemeHelper {
id: themeHelper
onThemeColorChanged: {
console.log("onThemeColorChanged:", themeColor);
}
onColorSchemeChanged: {
setWindowTitleBarMode(window, themeHelper.colorScheme == SystemThemeHelper.Dark)
console.log("onColorSchemeChanged:", colorScheme);
}
Component.onCompleted: {
console.log("onColorSchemeChanged:", colorScheme);
setWindowTitleBarMode(window, themeHelper.colorScheme == SystemThemeHelper.Dark)
}
} Text {
anchors.centerIn: parent
text: qsTr("主题颜色")
font.family: "微软雅黑"
font.pointSize: 32
color: themeHelper.themeColor
}
}

【结语】

通过SystemThemeHelper类,我们可以在 Qt 应用程序中实现系统主题感知功能。

这个类封装了读取系统设置、更新主题颜色和颜色方案以及发出通知的逻辑,使得我们可以轻松地根据系统主题变化来调整应用程序的外观。

此外,通过条件编译和平台特定的实现,还确保了跨平台的兼容性。

最后:项目链接(多多star呀.._):

Github: https://github.com/mengps/QmlControls

Gitee: https://gitee.com/MenPenS/QmlControls

Qt 中实现系统主题感知的更多相关文章

  1. QT中自定义系统托盘的实现—c++语言为例

    将要介绍的是:QT中自定义系统托盘(systemtray)的一个Demo,希望能帮需要的读者快速上手. 前提假设是诸位已经知道QT中的signals .slot以及资源文件,所以关于这些不会再累述. ...

  2. Qt 框架的图形性能高(OpenGL上的系统效率高),网络性能低,开发效率高,Quick是可以走硬件加速——Qt中分为好几套图形系统,差不多代表了2D描画的发展史。最经典的软描画系统

    -----图形性能部分-----Qt的widgets部分,运行时的图像渲染性能是一般的,因为大部分的界面内容都是Qt自绘,没有走硬件加速,也就是说很多图形内容都是CPU算出来的.但是widgets底层 ...

  3. Qt 中的属性系统(Property System)

    21 人赞同了该文章 本节内容主要讲解我对 Qt 属性系统的理解.官方文档参考 The Property System. 如何理解"属性系统"这个概念? 一般我们说一个类有什么属性 ...

  4. Qt 中的事件处理(一)

    1.图形界面应用程序的消息处理模型 特点: 基于操作系统才能运行 GUI应用程序提供的功能必须由用户触发 用户操作界面时操作系统是第一个感知的 系统内核的消息通过事件处理转变成QT的信号 2. Qt中 ...

  5. QT 中 关键字讲解(emit,signal,slot)

    Qt中的类库有接近一半是从基类QObject上继承下来,信号与反应槽(signals/slot)机制就是用来在QObject类或其子类间通讯的方法.作为一种通用的处理机制,信号与反应槽非常灵活,可以携 ...

  6. 第39课 Qt中的事件处理(下)

    1. 事件的传递过程 (1)操作系统检测到用户动作时,会产生一条系统消息,该消息被发送到Qt应用程序 (2)Qt应用程序收到系统消息后,将其转化为一个对应的QEvent事件对象,并调用QObject: ...

  7. 第38课 Qt中的事件处理(上)

    1. GUI程序原理回顾 (1)图形界面应用程序的消息处理模型 (2)思考:操作系统发送的消息如何转变为Qt信号 2. Qt中的事件处理 (1)Qt平台将系统产生的消息转换为Qt事件 ①Qt事件是一个 ...

  8. 第32课 Qt中的文件操作

    1. Qt的中IO操作 (1)Qt中IO操作的处理方式 ①Qt通过统一的接口简化了文件和外部设备的操作方式 ②Qt中的文件被看作一种特殊的外部设备 ③Qt中的文件操作与外部设备的操作相同 (2)IO操 ...

  9. QT中静态库的生成与使用

    一. 静态库的生成    1. 测试目录: lib    2. 源码文件名: mywindow.h, mywindow.cpp, 类MyWindow继承于QPushButton, 并将文字设置为&qu ...

  10. Qt之资源系统

    简述 Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制.当你的程序总需要这样的一系列文件(图标.翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用. 资源系统基于 q ...

随机推荐

  1. QToss:基于.NET架构的跨境电商的工具,助力企业实现智能数据营销

    2024年10月13日下午参加了一场在深圳举办的跨境电商大佬们的聚会,现场参加的人数上千人. 大会分享嘉宾中有位来自美国的,他告诉我们不用担心美国政府会把TikTok禁掉,TikTok在全世界都很受欢 ...

  2. js中window全局变量

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  3. 【总结】线性dp的几种重要模型

    当前点定义 \(f[i]\) :走到第 \(i\) 个点的方案数 / 最值. \(f[i][j]\) :走到第 \(i\) 个点,选了 \(j\) 个的答案. 依据题目的限制个数可以继续添加维数,也可 ...

  4. ABC 364

    ABC 364 E - Maximum Glutton 给定 \((a_i,b_i),X,Y\), 记 \(k\) 是第一个让 \(\sum_{i=1}^{k} a_i > X\) 或 \(\s ...

  5. Chapter12 动态内存与智能指针

    目录 12.1.0 介绍 12.1.1 shared_ptr类 make_shared函数 shared_ptr的拷贝和引用 shared_ptr自动销毁所管理的对象- -shared_ptr还会自动 ...

  6. 1000%增长!我仅用一个小时搞定!AI智能体+AI小程序=MVP王炸组合!

    前言 在万圣节的前一晚上10月30日,一位运营朋友跟我说了个点子万圣节头像生成器,然后大概给我分析了下整体思路,于是我用扣子Coze平台(coze.cn)搭建了一个AI智能体整个过程花了一个小时就搞定 ...

  7. jmeter如何产生批量数据?

    在使用jmeter时,若需要产生批量数据,可以通过连接数据库--执行相关sql进行操作,例如添加20条数据 步骤: 1.添加jdbc 数据库配置信息 2.添加循环控制器(循环次数20) 3.循环控制器 ...

  8. (Redis基础教程之十一) 如何使Redis中的Key过期

    介绍 Redis是一个开源的内存中键值数据存储.默认情况下,Redis密钥是_永久性_的,这意味着Redis服务器将继续存储它们,除非手动将其删除.但是,在某些情况下,您已经设置了密钥,但是您知道要在 ...

  9. Java深度历险(九)——Java安全

    安全性是Java应用程序的非功能性需求的重要组成部分,如同其它的非功能性需求一样,安全性很容易被开发人员所忽略.当然,对于Java EE的开发人员来说,安全性的话题可能没那么陌生,用户认证和授权可能是 ...

  10. golang之增加版本信息功能

    在实际开发中,当开发完一个 apiserver 特性后,会编译 apiserver 二进制文件并发布到生产环境,很多时候为了定位问题和出于安全目的(不能发错版本),我们需要知道当前 apiserver ...