19 March 2014

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

iStodo corporate blogQt
Сегодня я хотел бы рассмотреть вопрос кастомизации интерфейса программ на 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 с отображением в реальном времени.
Tags:qtqssинтерфейс
Hubs: iStodo corporate blog Qt
+5
36.3k 95
Comments 8
Top of the last 24 hours