Кроссплатформенное приложение на Qt: Таблицы стилей

  • Tutorial
Сегодня я хотел бы рассмотреть вопрос кастомизации интерфейса программ на Qt. Основным вариантом является использование Qt Style Sheets. Как становится очевидно из названия, это немного специфичный аналог привычных всем каскадных таблиц стилей (CSS2), без которых немыслим современный интернет. QSS чаще всего применяются для двух вещей: либо доточить какой-нибудь виджет до более родного вида, либо наоборот, сделать интерфейс более нарядным, выделяющимся, возможно одинаковым на всех платформах.


На скриншоте приведен один из диалогов нашего органайзера для студентов, весь интерфейс которого реализован на QSS.

На том же скриншоте мы видим основные элементы интерфейса, кастомизацию которых сегодня и разберем:
  • Сегментные кнопки
  • Обычные кнопки
  • Таблицы
  • Скроллбары

Основы


Но сначала разберемся с некоторыми базовыми вещами. Мы можем применять стили как локально (к конкретному виджету), так и глобально, вынося все стили в отдельный файл. Преимущества второго подхода очевидны всем, кто хоть раз работал с CSS, потому на нем мы и остановимся.
Я категорически рекомендую пользоваться системой ресурсов Qt, которая позволяет не заморачиваться и вкомпилировать картинки и файлы стилей прямо в бинарник.
Итак, создаем файл стилей, применяем его глобально к приложению:
QFile styleF;

styleF.setFileName(":/qss/style.css");
styleF.open(QFile::ReadOnly);
QString qssStr = styleF.readAll();

qApp->setStyleSheet(qssStr);

Есть отличный перевод официальной документации по синтаксису таблиц стилей, так что замечу лишь, что чаще других используется три способа описания виджетов, к которым будет применен стиль (селекторы):

Обращение по классу, например такое правило будет применено ко всем кнопкам вашего приложения, цвет фона будет изменен на красный:
QPushButton { background-color: red; }

Обращение по имени, тогда это правило будет применено только к тем кнопкам, которые имеют имя «okButton»:
QPushButton#okButton  { background-color: red; }
Причем если на одной и той же форме есть несколько элементов, к которым нужно применить одинаковый стиль, можно воспользоваться методом setObjectName:
ui->pbRed1->setObjectName("myRedButton");
ui->pbRed2->setObjectName("myRedButton");

Обращение по иерархии виджетов на форме, тогда правило будет применено только к тем кнопкам, которые лежат внутри рамки с именем «mainFrame»:
QFrame#mainFrame QPushButton{ background-color: red; }

Отдельно нужно сказать о задании стилей для подэлементов и псевдо-состояний:

Псевдо-состояния указываются в конце селектора, отделяются двоеточием (:). Например, следующее правило применяется, когда мышь находится над QPushButton:
QPushButton:hover { background-color: white }

Сложные виджеты содержат в себе подэлементы, потому для изменения оформления можно получить доступ к каждому из них, для этого используется оператор "::". Например, мы хотим изменить/убрать стрелочку, показывающую, что кнопке назначено меню, в таком случае правило будет выглядеть следующим образом:
QPushButton#menuButton::menu-indicator {
    image: url(:/img/other/myindicator.png);
}

К сожалению, чтобы описать все возможности потребуется полнокровный учебник по CSS, потому переходим к примерам.

Кнопки сегментные


У меня две новости: одна хорошая, а другая не очень. Печаль в том, что QSS не поддерживает отрисовку теней. Совсем. Потому если сделать обычное состояние сегментных кнопок в общем-то не сложно, то нажатое… в общем только картинки.
Вот стили, ответственные за это безобразие:
QPushButton#pbDelRight {
    image: url(:/img/buttons//pbDelRight.png);
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
    height: 26px;
    width: 40px;
}
QPushButton#pbDelRight:pressed {
    image: url(:/img/buttons/pbDelRightPressed.png);
}
QPushButton#pbDelRight:checked {
    image: url(:/img/buttons/pbDelRightPressed.png);
}

Состояние :pressed нужно указывать для того, чтобы не было лага при его прохождении.
OS X bug: Если не выставить кнопкам свойство flat, они будут налезать друг на друга.

Кнопки обыкновенные


Мне известны только два варианта отображения нажатого состояния без использования теней — отразить градиент и обратить цвета. Рассмотрим второй вариант:
 QPushButton#pbReady {
    padding:4px;
    color: #fff;
    font-size: 14px;
    border-radius: 2px;
    border: 1px solid #3873d9;
    background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1,
                                        stop: 0 #4287ff, stop: 1 #356ccc);
 }
QPushButton#pbReady:pressed {
    color: #111;
    border: 1px solid #3873d9;
    background: #fff;
 }


Таблицы


Кастомизация таблиц сделана весьма добротно, отдельно задаются стили для шапки, отдельно для самой таблицы. Есть возможность задать стили для определенных колонок (секций) шапки через подэлемент ::section. Для этого реализованы псевдо-состояния :first, :last, :only-one, :next-selected, :previous-selected, :selected, :horizontal, :vertical и :checked.
QHeaderView {
    background-color: #fff;
    font-size:13px;
}
QHeaderView::section:horizontal {
    color: #fff;
    border-style: solid;
    background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1,
                                        stop: 0 #4287ff, stop: 1 #356ccc);
 }
QTableView {
    border: 2px solid #3873d9;
    border-top-color: #4287ff;
    border-radius: 4px;
    background-color: #fff;
    background-image: url(:/img/other/background.png);
    gridline-color: #777;
    selection-background-color: #ccdfff;
    color:#333;
    font-size:12px;
 }

Скроллбары


Основными настраиваемыми элементами являются две стрелочки по краям и хэндл, за который, собственно можно ухватить мышкой:
 QScrollBar:vertical {
     background: #e4e4e4;
     border-top-right-radius: 4px;
     border-bottom-right-radius: 4px;
     width: 12px;
     margin: 0px;
 }
 QScrollBar::handle:vertical {
    background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 0,
                                    stop: 0 #4287ff, stop: 1 #356ccc);
     border-radius: 4px;
     min-height: 20px;
     margin: 0px 2px 0px 2px;
 }
 QScrollBar::add-line:vertical {
     background: none;
     height: 0px;
     subcontrol-position: right;
     subcontrol-origin: margin;
 }
 QScrollBar::sub-line:vertical {
     background: none;
     height: 0px;
     subcontrol-position: left;
     subcontrol-origin: margin;
 }

На OS X начиная с 10.9 используются волшебные исчезающие скроллбары, Qt их поддерживает, так что переопределять не стоит.
Со скроллбарами связан кроссплатформенный баг, из-за которого нижний/правый край скроллбара отрисовывается поверх объекта скроллинга, если тот имеет обводку через QSS.

Оглавление цикла

P.S. Примеры использования QSS при кастомизации каждого конкретного виджета можно посмотреть здесь, справочник возможных свойств, состояний и подэлементов тут. Ну и статья на хабре с попыткой реализации Ribbon-интерфейса, возможно будет интересно почитать.
P.P.S. Интересный инструмент для редактирования QSS с отображением в реальном времени.
  • +5
  • 31.1k
  • 8
iStodo
33.32
Company
Share post

Comments 8

    +4
    сделать интерфейс более нарядным, выделяющимся, возможно одинаковым на всех платформах.

    Обычно «нарядно» превращается в «вырвиглазно» и не согласуется с системной темой, у которой могут быть инвертированные цвета или основной цвет интерфейса, дисгармонирующий с выбранным для дизайна программы. Нативные интерфейсы более узнаваемы и легче воспринимаются; кроме того, они учитывают особенности UI данной ОС вроде расположения кнопок в диалогах ( habrahabr.ru/post/111345/ ).
      0
      Обычно «нарядно» превращается в «вырвиглазно»
      Это не потому, что инструмент плохой, просто использовать его правильно сложно.
      Нативные интерфейсы более узнаваемы и легче воспринимаются;
      Никто и не отвергает нативных интерфейсов, в том же предложении о них сказано.
      кроме того, они учитывают особенности UI данной ОС вроде расположения кнопок в диалогах
      От использования QSS кнопки порядок не изменят, правда ведь? Я ничуть не против нативных интерфейсов, но случаи бывают разные. Не обязательно переопределять вообще все, можно подчеркнуть какие-нибудь детали, устранить недостатки отображения.
      0
      Жалко что для своих кастомных виджетов нельзя реализовать кастомные стили.
      Вернее в QSS нельзя внести новые элементы для своих виджетов.
        +1
        Скачал ваше приложение поигрался. Честное слово, ваши эксперименты с цветом кнопок и скроллбаров не оправданы с точки зрения дизайна — приложение выглядит как java-апплет.
        Сисетемная тема на то и системная, что узнаваемая.
        +2
        QSS — зло, которое затягивает.
        Крайне не гибкое решение с кучей костылей, чего стоит, хотя бы, одна только невозможность отменить примененный Stylesheet для конкретного виджета.

        По моему мнению, если хочется свой стиль, и хочется его действительно хорошо и расширяемо проработать — стоит создавать своего наследника QStyle, не обязательно прямого, лучше всего отнаследоваться от какого-нибудь общего предка и вносить нужные изменения.
          +1
          QProxyStyle можно использовать и переопределить функцию отрисовки для конкретных элементов, а для всех остальных останется системный стиль либо выбранная тема.
          +1
          Мне видится использование QSS целесообразным в следующих случаях:
          • когда предполагается использование приложения на разных платформах одновременно: например пользователь часто работает с приложением на desktop системе на рабочем месте и с планшетного компьютера «в поле» (для iStodo — дома и на лекциях) и компоновка элементов остается прежней. Тогда возможно пользователю легче будет принять один интерфейс, чем постоянно «переключаться» с одного на другой. Другое дело, когда используется смартфон, тогда организация интерфейса все равно меняется (например с горизонтальной на вертикальную) и возможно не имеет смысла повторять стиль интерфейса desktop приложения.
          • для реализации поддержки скинов (skins) в приложениях на подобии аудиоплееров, приложений обмена мгновенными сообщениями и т.д. Один мой знакомый был одержим идеей переделки темы Windows XP в Mac OSX и для всех приложений он искал соответствующие темы (для Aimp, Mirinda, Firefox). Наличие «шкурки» было для него решающим в выборе того или иного софта. Другой знакомый решил сделать подарок подруге — именной скин под Aimp с сердечками и рюшечками в знак выражения своей любви. Не знаю, долго ли пользовалась его подруга этим скином, но она была в восторге. Эту потенциальную аудиторию тоже стоит иметь в виду.

          Но в любом случае (на мой взгляд) необходимо оставить возможность пользователю установить «родной» стиль системы, а для этого нужно оценивать usability как стилизованного интерфейса, так и нативного. Мне это видится как пункты меню «Settings->Style->» в котором появляются Ваши стили (зашитые в ресурсы) и стили сторонних разработчиков, обнаруженные в соответствующей папке.
          Конкретно для iStodo кому-то возможно вместо темы «blue» захочется написать тему «dark». Возможно даже человек напишет о этой теме в блоге, как например для QtCreator это сделал shedward в своей статье Редизайн QtCreator своими руками (еще одна хорошая статья по QSS).

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