Глава 26 – Элементы со стилем

Сталкивались ли вы с тем, что приложение “вроде работает”, но выглядит так, будто его собрали из разных эпох и платформ? Каждый разработчик знает это чувство: функционал уже готов, а look & feel внезапно превращается в тихую катастрофу для доверия пользователя.

Эта глава раскроет неочевидный факт: внешний вид — не “косметика”, а часть продаж и UX-логики. Здесь обнаружите, как добиваться нативности на разных ОС и при этом сохранять узнаваемый стиль продукта — без переписывания половины UI.

В главе будут 3 встроенных стиля (включая Fusion), переключение стиля “на лету” через QStyleFactory, и точечная кастомизация через QStyle / QCommonStyle и методы drawPrimitive(), drawControl(), drawComplexControl(). Плюс — QSS (Qt CSS): селекторы, состояния :hover/:pressed/:checked, подэлементы и border-image без перекомпиляции.

Если стиль вашего приложения до сих пор “по умолчанию”, вы рискуете проиграть еще до первого клика.

В этой главе вы найдёте готовые к использованию примеры кода.

Самопроверка по главе

Почему в Qt стили не зависят от кода программы и какое преимущество это дает для разработки?Ответ
Правильный ответ: Это позволяет разделить команды на программистов и дизайнеров, которые работают независимо; созданные стили можно использовать в других Qt-проектах без изменения исходного кода.
Какой класс следует наследовать для создания собственного стиля и почему не наследуют напрямую QStyle?Ответ
Правильный ответ: Предпочтительнее наследовать QCommonStyle, так как он определяет часто используемые методы и требует меньше усилий, чем переопределение всех методов абстрактного класса QStyle.
Зачем в методе polish() устанавливается атрибут Qt::WA_Hover для кнопок?Ответ
Правильный ответ: Чтобы Qt создавала событие перерисовки каждый раз, когда указатель мыши попадает в область кнопки, что позволяет изменять ее внешний вид при наведении.
Что произойдет, если создать объект стиля через new и передать его в setStyle(), забыв потом вызвать delete?Ответ
Правильный ответ: Утечки памяти не произойдет, так как класс QApplication берет на себя ответственность за уничтожение объектов стилей и автоматически удаляет старый объект при установке нового.
Какую ключевую роль играет класс QStyleOption при рисовании элементов управления?Ответ
Правильный ответ: Он передает всю необходимую информацию о виджете (состояние, размеры, шрифт, палитру) методам рисования, позволяя корректно отображать элемент без прямого доступа к виджету.
В чем принципиальная разница между методами drawPrimitive(), drawControl() и drawComplexControl()?Ответ
Правильный ответ: drawPrimitive() рисует простые элементы (индикаторы, рамки), drawControl() — основные виджеты (кнопки, прогресс-бары), а drawComplexControl() — составные элементы из нескольких частей (полосы прокрутки, счетчики).
Почему использование CSS-стилей считается более эффективным подходом, чем наследование классов стилей на C++?Ответ
Правильный ответ: CSS не требует знания C++, позволяет привлечь дизайнеров без навыков программирования и не требует перекомпиляции при изменении стиля, что значительно ускоряет разработку.
Что произойдет, если в CSS написать QPushButton вместо .QPushButton?Ответ
Правильный ответ: Стиль будет применен ко всем классам, унаследованным от QPushButton; точка перед именем класса исключает применение стиля к унаследованным классам.
Зачем нужно свойство border-image с указанием 9 частей изображения при стилизации кнопок?Ответ
Правильный ответ: Оно позволяет защитить угловые части (например, закругления) от деформации при изменении размера кнопки, растягивая только боковые и центральную части изображения.
Как применить CSS-стиль только к виджетам с конкретным именем объекта, установленным через setObjectName()?Ответ
Правильный ответ: Использовать синтаксис Класс#ИмяОбъекта, например: QLabel#MyLabel применит стиль только к QLabel с именем объекта MyLabel.
Что случится, если объединить состояния через запятую: QCheckBox:hover, QCheckBox:checked?Ответ
Правильный ответ: Стиль будет применяться, когда виджет находится в ЛЮБОМ из указанных состояний (логическое ИЛИ), а не одновременно в обоих.
Почему в примерах с градиентами для состояния :pressed использовался горизонтальный градиент вместо вертикального?Ответ
Правильный ответ: Чтобы пользователь сразу заметил визуальную разницу и понял, что состояние кнопки изменилось — горизонтальное направление создает контраст с обычным вертикальным градиентом.
Как в Qt6 активировать темную тему, используя встроенный стиль Fusion?Ответ
Правильный ответ: Установить стиль Fusion через setStyle(), затем создать темную палитру QPalette с настройками цветов для Window, Text, Button и других ролей, и применить её через setPalette().
Для чего используется селектор с двойным двоеточием, например QComboBox::drop-down?Ответ
Правильный ответ: Для доступа к подэлементам составных виджетов — в данном случае к кнопке со стрелкой выпадающего списка, что позволяет стилизовать части виджета независимо.
Почему автор главы подчеркивает важность внешнего вида программы, сравнивая её с выбором автомобиля или знакомством с людьми?Ответ
Правильный ответ: Внешний вид — первое, что видит пользователь; привлекательный интерфейс вызывает желание попробовать программу, без чего пользователь никогда не оценит её функциональные возможности.

Практические задания

Простой уровень

Переключатель стилей в реальном времени
Создайте приложение с несколькими виджетами (кнопка, чекбокс, слайдер, поле ввода) и выпадающим списком для выбора стиля. При выборе стиля из списка все виджеты должны мгновенно изменять свой внешний вид. Добавьте все доступные встроенные стили Qt.
Подсказки: Используйте QStyleFactory::keys() для получения списка стилей. Создайте QComboBox и подключите сигнал textActivated к слоту, который вызывает setStyle(QStyleFactory::create()). Разместите виджеты в вертикальной компоновке.

Средний уровень

Кнопка с CSS-анимацией состояний
Создайте кнопку с переключением (checkable), которая имеет разные визуальные состояния: обычное, при наведении мыши, нажатое, активированное и активированное с наведением. Используйте CSS с градиентами и эффектами прозрачности. Кнопка должна показывать текущее состояние (например, “ON” или “OFF”).
Подсказки: Используйте setCheckable(true) для кнопки. В CSS определите стили для состояний :hover, :pressed, :checked и :checked:hover. Примените qlineargradient для градиентов. Используйте функцию rgba() для прозрачности. Подключите сигнал toggled к слоту для изменения текста кнопки.

Сложный уровень

Собственный стиль с переопределением drawPrimitive()
Создайте собственный класс стиля, наследующий QCommonStyle, который кастомизирует отображение кнопок, чекбоксов и радиокнопок. Переопределите методы drawPrimitive() и polish()/unpolish(). Реализуйте загрузку изображений из ресурсов для разных состояний элементов (обычное, hover, pressed). Добавьте возможность переключения между вашим стилем и стандартными стилями Qt.
Подсказки: Создайте класс, наследующий QCommonStyle. В polish() установите Qt::WA_Hover для виджетов. В drawPrimitive() обработайте PE_PanelButtonCommand, PE_IndicatorCheckBox, PE_IndicatorRadioButton. Используйте qstyleoption_cast для получения информации о состоянии. Проверяйте флаги State_Sunken, State_MouseOver, State_Enabled. Создайте файл ресурсов (.qrc) с изображениями. В секции default вызывайте QCommonStyle::drawPrimitive().

💬 Присоединяйтесь к обсуждению!

Разобрались с различием между QStyle и CSS-подходом? Возникли вопросы о том, когда лучше использовать drawPrimitive(), а когда достаточно CSS-стилей?

Поделитесь своими примерами стилизации, покажите созданные дизайны кнопок или задайте вопросы о градиентах и border-image!

Обсудите с другими читателями, как вы применяете принципы “красивого интерфейса” в своих проектах. Ваш опыт может помочь начинающим разработчикам!

Leave a Reply

Your email address will not be published. Required fields are marked *