Qt加载.css/.qss文件设置控件的QSS样式(支持程序运行时修改且立即生效类似换肤效果)

初学Qt时,你是如何设置QWidget,QPushButton等原生基础控件的样式的?是不是主要是两种方法?

1.直接在可视化的.ui文件中直接添加qss语句。

2.在代码中通过setStyleSheet(QString qss)来设置qss语句。

上述两种方法,在程序规模很大时,很多地方需要复用样式会非常麻烦,qss语句写的到处都是,极难维护,要你改一个按钮样式你都要到处翻,还要一个个改,烦到想死!

于是,

为了更好地管理样式,提高复用率,应该把QSS样式语句写在一个个文件中(文件后缀是.css或者.qss都可以,但是建议保存为.css文件好点,因为Notepad++可以进行语法识别高亮提醒,另外样式相关的文件编码最好是UTF-8带BOM),程序初始化时统一加载到主程序中,这样所有控件都会自动继承,且通过属性过滤器决定哪个控件生效(如下图)。

我喜欢根据Qt原生支持QSS语句改变样式的基础控件都单独一个.css文件,例如QPushButton.css、QWidget.css、QLabel.css等。。。你也可以根据你自己程序的每个窗口一个.css文件,随你喜欢,我只是给你一种加载样式的思路。

.css文件在Notepad++中能被传统的CSS的语法识别,获得传统CSS语法的高亮染色显示/自动补全提示等的支持,但是.qss文件是不行的和.txt文本没有区别:

建议使用动态属性标记样式,这样通过设置动态属性就可以复用该样式:

每个控件都可以设置多个动态属性,意味着可以叠加生效多个被动态属性标记的样式:

样式根文件StyleList.txt(文件名随意改)负责记录这些所有的.css文件名

这样通过读取样式根文件“StyleList.txt” 即可知有多少个.css/.qss样式文件可以加载,然后对这些样式文件一个个读取,然后将所有内容拼接成一个超长的QString,再使用setStyleSheet(QString qss)来设置加载到主程序中。期间也可以选择性使用QFileSystemWatcher来监控这些样式文件的内容变化,一旦有内容更新会发出信号,然后马上重新加载所有样式。

举例,我一个测试程序的exe文件在bin目录下,bin同级目录下有res/QSS来存放QSS样式相关文件

创建一个加载qss样式的工具类 “QssLoadTool” 用于加载,并监控这些文件的变化:

qssloadtool.h

#ifndef QSSLOADTOOL_H
#define QSSLOADTOOL_H

#include <QObject>
#include <QFile>
#include <QFileSystemWatcher>


class QssLoadTool : public QObject
{
    Q_OBJECT
public:
    explicit QssLoadTool(QObject *parent = nullptr);

    // 设置qss样式文件的根文件(根文件记录了需要加载的所有qss样式文件名)
    static void setQssFileListRootFile(const QString &QssRootFile);
    static QString getQssFileListRootFile();

    // 加载所有qss文件刷新程序控件样式
    static void LoadQss2RefreshStyle();

    // 监控qss相关文件,发送修改就重新加载(在main调用一次即可)
    static void WatchQSSFileChange(QFileSystemWatcher *FileWatcher);

private :

    // 本程序所需qss样式文件的根文件
    static QString m_QssRootFile;

    // 程序运行位置
    static QString m_currentPath;
};


#endif // QSSLOADTOOL_H

qssloadtool.cpp

#include "qssloadtool.h"
#include <QDebug>
#include <QApplication>

QString QssLoadTool::m_QssRootFile = "";

QssLoadTool::QssLoadTool(QObject *parent) : QObject(parent)
{
    // 获取应用程序当前路径
    m_currentPath = QCoreApplication::applicationDirPath();
}

void QssLoadTool::setQssFileListRootFile(const QString &QssRootFile)
{
    m_QssRootFile = QssRootFile;
}

QString QssLoadTool::getQssFileListRootFile()
{
    return m_QssRootFile;
}

void QssLoadTool::LoadQss2RefreshStyle()
{
    if(m_QssRootFile.isEmpty())
    {
        qDebug() << "未设置qss样式文件的根文件:" << m_QssRootFile;
        return;
    }

    qDebug() << __FUNCTION__ << "qss样式发送变更,正在重新加载...";

    QFile file(m_QssRootFile);
    if (file.open(QIODevice::ReadOnly))
    {

       QString style = file.readAll();
       file.close();

       QStringList styleList = style.split("\n");
       style.clear();
       QString path = "";
       for(const QString &qssfile : styleList)
       {
           path = m_currentPath + "/../res/QSS/" + qssfile;

           file.setFileName(path.trimmed());
           if(file.open(QIODevice::ReadOnly))
           {
               style = style + file.readAll().trimmed();
               file.close();
           }
           else
           {
               qDebug() << "打开文件失败! ---> " << path;
           }
       }

       qobject_cast<QApplication*>(QApplication::instance())->setStyleSheet(style);

    }
}

void QssLoadTool::WatchQSSFileChange(QFileSystemWatcher *FileWatcher)
{
    if(m_QssRootFile.isEmpty())
    {
        qDebug() << "未设置qss样式文件的根文件:" << m_QssRootFile;
        return;
    }

    FileWatcher->addPath(m_QssRootFile);

    qDebug() << "监控qss样式文件的根文件:" << m_QssRootFile;

    QFile file(m_QssRootFile);
    if (file.open(QIODevice::ReadOnly))
    {
       QString files = file.readAll();
       file.close();
       QStringList fileList = files.split("\n");
       QString path = "";
       for(const QString &qssfile : fileList)
       {
           path = m_currentPath + "/../res/QSS/" + qssfile;
           FileWatcher->addPath(path.trimmed());

           qDebug() <<"监控qss样式文件 :" << path.trimmed();
       }
    }

    // 被监控的qss文件发生修改时,马上重新加载所有qss样式文件
    QObject::connect(FileWatcher, &QFileSystemWatcher::fileChanged, [](){
        QssLoadTool::LoadQss2RefreshStyle();
    });
}

main.cpp中使用方式:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
        
    // 获取exe所在位置(用于拼接样式根文件的相对路径)
    QString currentPath = QCoreApplication::applicationDirPath();

    // 设置本程序的工作路径为可执行文件所在目录(不设置可能导致qss样式内用相对路径“../或者./”开头的url路径找不到位置从而无法加载图片等资源)
    QDir::setCurrent(currentPath);
    
    // 设置样式根文件
    QssLoadTool::setQssFileListRootFile(currentPath + "/../res/QSS/StyleList.txt");
    // 通过上一步设置的样式根文件去加载每一个.css样式文件
    QssLoadTool::LoadQss2RefreshStyle(); 
    
    // 监控所有样式文件的内容变化,一旦发生变化就马上刷新样式并生效
    // (如果不想监控,那就接下来的这两句代码不写)
    QFileSystemWatcher fileWatcher;
    QssLoadTool::WatchQSSFileChange(&fileWatcher);

    // 主界面启动
    ProjectMainWindow w;
    w.show();

    return a.exec();
}

注意:程序运行中,换肤操作后,控件的样式不生效时,说明需要显式调用样式初始化函数手动重新初始化,在合适的地方调用以下方法重新初始化控件样式。

void QStyle::polish(QWidget *widget)

void QStyle::polish(QApplication *application)

void QStyle::polish(QPalette &palette)

另外还可以取消控件的样式:

void QStyle::unpolish(QWidget *widget)

void QStyle::unpolish(QApplication *application)

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值