Отлаживаем Qt Style Sheet

  • Tutorial
Популярный фреймворк Qt имеет очень удобный механизм управления стилями UI — Qt Style Sheet. Благодаря которому стиль виджетов и окон можно задать в CSS-подобной форме. Стиль может храниться как в ресурсах приложения так и во внешенем файле.
В своей практике постоянно сталкивался с задачей отладить файл стиля в реальном проекте. Если для веб-приложений достаточно нажать F5 в браузере, то на десктопе придется перезапускать приложение, иногда авторизовываться, добираться до нужной формы. Это большая потеря времени. Попробуем сделать инструмент для удобной отладки стилей. Сформулирую пользовательский сценарий:

Хотим править файл стиля и сразу смотреть как это выглядит в любой форме приложения.


Приступая к реализации, будем считать, что пользователя устроит вариант — сохранить файл стиля — нажать кнопку обновления в приложении. Реализация будет опираться на механизм перехвата событий приложения (eventFilter). При старте приложения установим перехватчик событий, который будет перезагружать файл стиля при нажатии заданной клавиши.

Немного кода:
Описание класса
StyleLoader.h
#ifndef STYLELOADER_H
#define STYLELOADER_H

#include <QObject>
#include <QKeySequence>
class StyleLoader: public QObject
{
    Q_OBJECT
public:
    static void attach(const QString& filename = defaultStyleFile(),
                       QKeySequence key = QKeySequence("F5"));

    bool eventFilter(QObject *obj, QEvent *event);
private:
    StyleLoader(QObject * parent, const QString& filename, const QKeySequence& key);
    void setAppStyleSheet();
    static QString defaultStyleFile();
    QString m_filename;
    QKeySequence m_key;

};
#endif // STYLELOADER_H


и реализация:
#include "StyleLoader.h"
#include <QApplication>
#include <QFile>
#include <QKeyEvent>
#include <QDebug>

void StyleLoader::attach(const QString &filename, QKeySequence key)
{
    StyleLoader * loader = new StyleLoader(qApp, filename, key);
    qApp->installEventFilter(loader);
    loader->setAppStyleSheet();
}

bool StyleLoader::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if(m_key == QKeySequence(keyEvent->key()))
            setAppStyleSheet();
        return true;
    }
    else
        return QObject::eventFilter(obj, event);
}

void StyleLoader::setAppStyleSheet()
{
    QFile file(m_filename);
    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug() << "Cannot open stylesheet file " << m_filename;
        return;
    }
    QString stylesheet = QString::fromUtf8(file.readAll());
    qApp->setStyleSheet(stylesheet);
}

QString StyleLoader::defaultStyleFile()
{
    return QApplication::applicationDirPath() + "/style.qss";
}

StyleLoader::StyleLoader(QObject *parent, const QString& filename, const QKeySequence &key):
    QObject(parent),
    m_filename(filename),
    m_key(key)
{

}



Чтобы подключить инструмент к приложению, достаточно написать одну строчку где-нибудь в main():

StyleLoader::attach();

В таком варианте будут использоваться настройки по-умолчанию:
Файл стиля: Папка_с_исполняемым файлом/style.qss;
клавиша для обновления: F5.

Можно задать собственные значения:
StyleLoader::attach("c:/myStyle.qss", QKeySequence("F6"));


Теперь мы можем запустить наше приложение, в любой момент подправить файл стиля, нажать F5 и сразу же увидеть как это будет выглядеть.

PS: код компактный, поэтому рекомендую просто стащить себе в проект. Вскоре выложу на Github под свободной лицензией.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 12

    +6
    А если использовать QFileSystemWatcher то можно обойтись даже без клавиш.
      +1
      Да, хорошая идея!
        0
        Я бы был все же за явное перечитывание стилей, а не так, что случайно сохранил файлик — и приложение запестрело. Так что если делать такое, то это должна быть отключаемая возможность.
          0
          Это в любом случае отладочный механизм. Из продакшена лучше все это убирать.
      +1
      Я когда-то делал встроенный в проект редактор QSS, выдирал его из Qt Creator'а =)
        0
        В реализации фильтра ошибка, виляющая на всё приложение. Фильтруются все QEvent::KeyPress, даже если m_key != QKeySequence(keyEvent->key()).
        Исправить можно примерно так:
        bool StyleLoader::eventFilter(QObject *obj, QEvent *event)
        {
            if (event->type() == QEvent::KeyPress)
            {
                QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
                if(m_key == QKeySequence(keyEvent->key()))
                {
                    setAppStyleSheet();
                    return true;
                }
            }
        
            return QObject::eventFilter(obj, event);
        }
        


        Добавление QFileSystemWatcher можно сделать описав его в заголовочном файле: QFileSystemWatcher watcher, объявив
        setAppStyleSheet как слот и добавив в конструктор код:
            watcher.addPath(filename);
            connect(&watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(setAppStyleSheet()));
        
          0
          В реализации фильтра ошибка, виляющая на всё приложение.
          Да, согласен. Спасибо.

          Добавление QFileSystemWatcher можно сделать описав его в заголовочном файле: QFileSystemWatcher watcher,
          Создавать объекты в заголовочном файле нужно острожно. Каждое включение такого хедера в единицу трансляции создаст отдельный объект.
            0
            Каждое включение такого хедера в единицу трансляции создаст отдельный объект.

            У нас включение этого заголовочника ожидается только в main.cpp, так что всё ок.
              0
              Если речь о цельном проекте — то конечно да, вообще без разницы где будет объявление. Но если вы даете исходники людям, вы им напишите — «включать только в main.cpp»? Никогда не встречал ничего подобного.

              Можно просто создать этот объект в конструкторе StyleLoader. Или прямо в теле StyleLoader.cpp на худой конец.
                0
                Чтобы подключить инструмент к приложению, достаточно написать одну строчку где-нибудь в main()


                Но если беспокоит чистота кода, то вот ещё утечка памяти:
                StyleLoader * loader = new StyleLoader(qApp, filename, key);
                
                  0
                  Чтобы подключить инструмент к приложению, достаточно написать одну строчку где-нибудь в main()
                  Или где-нибудь еще, это ж очевидно :)

                  Но если беспокоит чистота кода, то вот ещё утечка памяти:
                  Поясните, где тут утечка. StyleLoader — это QObject. В конструктор базового класса мы передали родителя (qApp), который разрушит всех своих чайлдов, когда у него вызовут деструктор.
          0
          В конструктор базового класса мы передали родителя (qApp), который разрушит всех своих чайлдов, когда у него вызовут деструктор.
          

          Согласен, эта часть корректна, утечки не будет.
          EDIT: ответ к habrahabr.ru/post/232323/#comment_8307877

          Only users with full accounts can post comments. Log in, please.