Некоторое время назад в одном из обсуждений я упомянул о том, что контрол «a-la ribbon» (который был использован в MS Office 2007 для организации меню) легко и непринуждённо реализуется средствами Qt.
Я не хочу спорить о том, удобен ribbon или нет (сам я больше склоняюсь ко второму мнению). Но на его примере можно отлично раскрыть возможности каскадных таблиц стилей для Qt (которые были представлены в Qt 4.2), чем я и займусь. Сразу прошу прощения: я не дизайнер, поэтому с точки зрения эстетики мой QRibbon скорее всего не дотянет до своего собрата от МС, но дизайнеры в МС, полагаю, в своей области превосходят меня на 2 головы, да и человеко-часов, думаю, там было затрачено в слегка побольше. Я же всего лишь демонстрирую общий принцип и базовые возможности.
Так как я сейчас изучаю язык Python, то для демонстрации был выбран именно он, но для C++ всё делается абсолютно так же. Заранее прошу прощения: Python я только-только изучаю, поэтому код может быть полон корявостей, так что прошу больно не пинать :)
Итак, начнём!
Возьмём стандартный виджет QTabWidget в качестве основы, т.к. он сам тут напрашивается. Страницы будем делать из виджетов, имеющих горизонтальный лэйаут (QHBoxLayout), последний элемент которого — QSpacerItem (не знаю, как назвать его по-русски, но это такая невидимая фиговина, которая заполняет свободное пространство :)).
Теперь открываем QtDesigner и рисуем панели, которые мы будем добавлять на «страницы» нашего риббона. Воспроизводить панель попиксельно в мои планы не входит, я просто покажу, как можно обыграть разные кнопки и раскрасить их с помощью таблицы стилей. Первую панель сделаем такой:

На ней будет большая кнопка Paste и 2 кнопки поменьше: Cut и Copy.
Вторая панель будет панелью шрифтов. Разместим на ней комбо-боксы для выбора семейства шрифта и его размера, а также «западающие» кнопки Bold, Italic и Underline:

Каждую из панелей мы сделаем отдельным виджетом (отдельным классом). Этот класс будет загружать свой ui-файл (который мы рисуем в дизайнере) и обрабатывать сигналы от контролов (эту часть я пропущу, как не относящуюся непосредственно к нашей теме).
Итак, добавим базовый класс панели (пока что он не несёт никакой нагрузки, но в дальнейшем через него можно будет задавать панелям некоторые общие свойства, как, например, стиль) и 2 класса панелей:
Ну и в главной функции добавим наши панели на страницу «Home»:
В результате получаем вот такое чудо:

Очертания вроде проглядывают, но риббон явно «не торт»… Спокойствие, только спокойствие, подходим к самому интересному!
Итак, создадим вспомогательную функцию, читающую и возвращающую содержимое файла (будем использовать её для чтения таблиц стилей из файлов) и добавим в конструкторы классов QRibbon и Pane строчку, читающую соответствующий файл стиля:
Думаю, будет лучше, если кнопки Cut, Copy и Paste сделать плоскими. Идём в QtDesigner и выставляем им соответствующий атрибут. Ну а теперь займёмся самым интересным: создаём CSS, которая преобразит наш риббон до неузнаваемости!
Сначала займёмся таблицей стилей для основного окна (qribbon.qss). Синтаксис 1-в-1 скопирован с CSS, поэтому я буду давать лишь общие комментарии, ибо код говорит сам за себя:
Установим цвет фона виджетов:
Сделаем верхнюю границу панелей более симпатичной:
Тут требуется небольшое пояснение. При помощи первых 4-х параметров задаётся направление: из левого-верхнего угла в левый нижний, т.е. вертикально вниз. Далее идут контрольные точки (принимают значения от 0 до 1) и их цвета. В данном случае у нас простой градиент: идёт от нуля до единицы без дополнительных промежуточных точек.
Зададим небольшой отступ для табов:
Зададим отступы для каждого из табов в таб-баре, а также изменим цвет текста и сделаем табам круглые уголки:
Изменим цвет таба при наведении курсора:
Здесь градиент чуть сложнее: он имеет 2 промежуточные «контрольные точки»: 0.4 и 0.5.
Зададим неактивному табу стиль нижней границы, совпадающий с верхней границей панелей (см. выше):
Изменим стиль активного таба и уберём его нижнюю границу:
Слегка изменим стиль кнопок:
Изменим цвет «утопленных» кнопок:
Спрячем «плоские» кнопки. Пусть будет видно только содержимое:
«Оживим» кнопки в момент нажатия:
Посмотрим что получилось:

Хм, вроде становится получше, правда же? Осталось придать немного шарма панелькам с контролами. Создадим файл page.qss:
Вот и всё.
Теперь вспомним то, как выглядел наш риббон до того, как мы применили стили и сравниваем с тем, как он выглядит теперь:


Неплохо, правда? :) (комбо-боксы, разумеется, тоже кастомизуются, но это я оставлю в качестве домашнего задания)
За подробной документацией по использованию стилей в Qt можно обращаться по адресу doc.trolltech.com/4.2/stylesheet.html
Единственное пожелаение тем, кто воспользуется данным вводным руководством и будет использовать стили в своих программах: помещайте стили во внешние файлы. В этом случае даже если вы слегка переусердствуете — пользователь сможет потюнить UI без необходимости разбирать программный код.
Архив с исходниками тут.
Я не хочу спорить о том, удобен ribbon или нет (сам я больше склоняюсь ко второму мнению). Но на его примере можно отлично раскрыть возможности каскадных таблиц стилей для Qt (которые были представлены в Qt 4.2), чем я и займусь. Сразу прошу прощения: я не дизайнер, поэтому с точки зрения эстетики мой QRibbon скорее всего не дотянет до своего собрата от МС, но дизайнеры в МС, полагаю, в своей области превосходят меня на 2 головы, да и человеко-часов, думаю, там было затрачено в слегка побольше. Я же всего лишь демонстрирую общий принцип и базовые возможности.
Так как я сейчас изучаю язык Python, то для демонстрации был выбран именно он, но для C++ всё делается абсолютно так же. Заранее прошу прощения: Python я только-только изучаю, поэтому код может быть полон корявостей, так что прошу больно не пинать :)
Итак, начнём!
Создаём скелет
Возьмём стандартный виджет QTabWidget в качестве основы, т.к. он сам тут напрашивается. Страницы будем делать из виджетов, имеющих горизонтальный лэйаут (QHBoxLayout), последний элемент которого — QSpacerItem (не знаю, как назвать его по-русски, но это такая невидимая фиговина, которая заполняет свободное пространство :)).
#!/usr/bin/python
from PyQt4 import Qt, uic
import sys
# Class that represents a ribbon page
class Tab(Qt.QWidget) :
def __init__(self) :
Qt.QWidget.__init__(self)
# create the spacer and the layout and set it as a widget main layout
self.spacer = Qt.QSpacerItem(10, 10, Qt.QSizePolicy.Expanding)
self.layout = Qt.QHBoxLayout()
self.layout.addSpacerItem(self.spacer)
self.setLayout(self.layout)
# Class that represents a ribbon
class QRibbon(Qt.QTabWidget) :
def __init__ (self) :
Qt.QTabWidget.__init__(self)
self.resize(450, 170)
# storage for tabs
self.tabs = dict()
def addTab(self, tabName) :
# check if tab with this name already exists
if not tabName in self.tabs :
newTab = Tab()
self.tabs[tabName] = newTab
Qt.QTabWidget.addTab(self, newTab, tabName)
def addPane(self, tabName, pane) :
# check if tab with this name exists
if tabName in self.tabs :
tab = self.tabs[tabName]
tab.layout.insertWidget(tab.layout.count() - 1, pane)
if __name__ == "__main__" :
app = Qt.QApplication(sys.argv)
ribbon = QRibbon()
# add a couple of tabs
ribbon.addTab(ribbon.tr('Home'))
ribbon.addTab(ribbon.tr('Insert'))
ribbon.show()
app.exec_()
Теперь открываем QtDesigner и рисуем панели, которые мы будем добавлять на «страницы» нашего риббона. Воспроизводить панель попиксельно в мои планы не входит, я просто покажу, как можно обыграть разные кнопки и раскрасить их с помощью таблицы стилей. Первую панель сделаем такой:

На ней будет большая кнопка Paste и 2 кнопки поменьше: Cut и Copy.
Вторая панель будет панелью шрифтов. Разместим на ней комбо-боксы для выбора семейства шрифта и его размера, а также «западающие» кнопки Bold, Italic и Underline:

Каждую из панелей мы сделаем отдельным виджетом (отдельным классом). Этот класс будет загружать свой ui-файл (который мы рисуем в дизайнере) и обрабатывать сигналы от контролов (эту часть я пропущу, как не относящуюся непосредственно к нашей теме).
Итак, добавим базовый класс панели (пока что он не несёт никакой нагрузки, но в дальнейшем через него можно будет задавать панелям некоторые общие свойства, как, например, стиль) и 2 класса панелей:
class Pane(Qt.QWidget) :
def __init__(self) :
Qt.QWidget.__init__(self)
class ClipboardPane(Pane) :
def __init__(self) :
Pane.__init__(self)
uiClass, qtBaseClass = uic.loadUiType('edit.ui')
self.ui = uiClass()
self.ui.setupUi(self)
# set icons for buttons
self.ui.pasteBtn.setIcon(Qt.QIcon('paste.png'))
self.ui.pasteBtn.setIconSize(Qt.QSize(48, 48))
self.ui.cutBtn.setIcon(Qt.QIcon('cut.png'))
self.ui.copyBtn.setIcon(Qt.QIcon('copy.png'))
class FontPane(Pane) :
def __init__(self) :
Pane.__init__(self)
uiClass, qtBaseClass = uic.loadUiType('font.ui')
self.ui = uiClass()
self.ui.setupUi(self)
Ну и в главной функции добавим наши панели на страницу «Home»:
if __name__ == "__main__" :
...
# add two panes to "Home" page
ribbon.addPane(ribbon.tr('Home'), ClipboardPane())
ribbon.addPane(ribbon.tr('Home'), FontPane())
...
app.exec_()
В результате получаем вот такое чудо:

«Стоп!» — думаю себе, — «что-то тут не так»....
Очертания вроде проглядывают, но риббон явно «не торт»… Спокойствие, только спокойствие, подходим к самому интересному!
Итак, создадим вспомогательную функцию, читающую и возвращающую содержимое файла (будем использовать её для чтения таблиц стилей из файлов) и добавим в конструкторы классов QRibbon и Pane строчку, читающую соответствующий файл стиля:
def readStyleSheet(fileName) :
css = Qt.QString()
file = Qt.QFile(fileName)
if file.open(Qt.QIODevice.ReadOnly) :
css = Qt.QString(file.readAll())
file.close()
return css
class QRibbon(Qt.QTabWidget) :
def __init__ (self) :
...
self.setStyleSheet(readStyleSheet('qribbon.qss'))
class FontPane(Pane) :
def __init__(self) :
...
self.setStyleSheet(readStyleSheet('page.qss'))
Думаю, будет лучше, если кнопки Cut, Copy и Paste сделать плоскими. Идём в QtDesigner и выставляем им соответствующий атрибут. Ну а теперь займёмся самым интересным: создаём CSS, которая преобразит наш риббон до неузнаваемости!
«Раскрась сам»
Сначала займёмся таблицей стилей для основного окна (qribbon.qss). Синтаксис 1-в-1 скопирован с CSS, поэтому я буду давать лишь общие комментарии, ибо код говорит сам за себя:
Установим цвет фона виджетов:
QWidget {
background-color: #d0d9f0;
}
Сделаем верхнюю границу панелей более симпатичной:
QTabWidget::pane {
border-top: 2px solid qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #90a0e0, stop:1.0 #303070);
}
Тут требуется небольшое пояснение. При помощи первых 4-х параметров задаётся направление: из левого-верхнего угла в левый нижний, т.е. вертикально вниз. Далее идут контрольные точки (принимают значения от 0 до 1) и их цвета. В данном случае у нас простой градиент: идёт от нуля до единицы без дополнительных промежуточных точек.
Зададим небольшой отступ для табов:
QTabWidget::tab-bar {
left: 30px;
}
Зададим отступы для каждого из табов в таб-баре, а также изменим цвет текста и сделаем табам круглые уголки:
QTabBar::tab {
padding: 5px 15px 3px 15px;
margin-top: 10px;
color: #303070;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
Изменим цвет таба при наведении курсора:
QTabBar::tab:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #fffaff, stop: 0.4 #fff0c0,
stop: 0.5 #fff0c0, stop: 1.0 #d0d9f0);
border: 1px solid #a4a063;
}
Здесь градиент чуть сложнее: он имеет 2 промежуточные «контрольные точки»: 0.4 и 0.5.
Зададим неактивному табу стиль нижней границы, совпадающий с верхней границей панелей (см. выше):
QTabBar::tab:!selected {
border-bottom: 2px solid qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #90a0e0, stop:1 #303070);
}
Изменим стиль активного таба и уберём его нижнюю границу:
QTabBar::tab:selected {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #f0f0ff, stop: 0.4 #f4f4ff,
stop: 0.5 #e7e7ff, stop: 1.0 #d0d9f0);
border: 1px solid #808090;
border-bottom: solid 0px;
}
Слегка изменим стиль кнопок:
QPushButton {
background-color:
qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #f6fcff, stop: 1.0 #a0b0d0);
border: 1px solid #a0a0b0;
border-radius: 3px;
}
Изменим цвет «утопленных» кнопок:
QPushButton:checked {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #f0eeaa, stop: 1 #eeaa88);
}
Спрячем «плоские» кнопки. Пусть будет видно только содержимое:
QPushButton:flat {
border: none;
background: none;
}
«Оживим» кнопки в момент нажатия:
QPushButton:pressed {
background-color: #e0e3ff;
}
Посмотрим что получилось:

Хм, вроде становится получше, правда же? Осталось придать немного шарма панелькам с контролами. Создадим файл page.qss:
QFrame {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #f6fcff, stop: 1.0 #a0b0d0);
border: 1px solid #8080a0;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
QLabel {
color: #303070;
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #c6cccf, stop: 1.0 #90a0b0);
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top: 0px;
}
Вот и всё.
Теперь вспомним то, как выглядел наш риббон до того, как мы применили стили и сравниваем с тем, как он выглядит теперь:


Неплохо, правда? :) (комбо-боксы, разумеется, тоже кастомизуются, но это я оставлю в качестве домашнего задания)
За подробной документацией по использованию стилей в Qt можно обращаться по адресу doc.trolltech.com/4.2/stylesheet.html
Единственное пожелаение тем, кто воспользуется данным вводным руководством и будет использовать стили в своих программах: помещайте стили во внешние файлы. В этом случае даже если вы слегка переусердствуете — пользователь сможет потюнить UI без необходимости разбирать программный код.
Архив с исходниками тут.