QT 主题机制详解
概念理解
Qt Platform Plugin
Qt 文档中所述:
Qt平台抽象(QPA)是Qt中主要的平台抽象层。
QPA 插件是通过对各种
QPlatform*
类进行子类化来实现的。有几个根类,例如QPlatformIntegration
和 用于窗口系统集成以及QPlatformTheme
更深层次的平台主题和QPlatformWindow
集成。QStyle 不属于 QPA,旨在不修改的 Qt 应用程序而能使用尽可能多的平台主题设置,它允许 Qt 应用程序尽可能地适应系统环境。
Qt 提供了两个用于创建插件的 API:
- 用于扩展 Qt 本身的高级 API:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等。
- 用于扩展 Qt 应用程序的低级 API。
编写扩展 Qt 本身的插件步骤:
- 继承适当的插件基类
- 实现一些功能
- 添加一个对应的宏
在开发过程中,插件的目录是 QT_INSTALL_DIR/plugins
(其中 QT_INSTALL_DIR 是安装 Qt 的目录),每种类型的插件都在该类型的子目录中。
Qt Platform Theme (QPT)
在 Linux 系统中可以通过 QPlatformThemePlugin 类来创建平台主题。
QPT 与视觉风格没有太大关系,其主要负责与平台底层交互相关设置,包括如下几个方面:
- 设置对话框、字体、图标以及控件的交互等。
- 定义标准对话框的外观。
- 定义应用程序正在使用的调色板(QPaletee)。
编写扩展 QPT 插件步骤:
- 继承 QPlatformThemePlugin;
- 增加描述插件的元数据的 json 文件;
- 平台插件编译成库安装在 Qt 加载平台插件的指定目录
QT_INSTALL_DIR/plugins/platformthemes/
。
如下为常用接口说明:
// 返回平台的主题样式设置。ThemeHint 是一个枚举,其中定义了可以设置的主题属性
virtual QVariant themeHint(ThemeHint hint) const;
// 是否使用平台对话框
virtual bool usePlatformNativeDialog(DialogType type) const;
// 该接口返回了平台对话框 Helper,通过 helper 连接了平台对话框与 Qt 的对话框。
virtual QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const;
// 创建对应 Palette 类型的调色板。
virtual const QPalette *palette(Palette type = SystemPalette) const;
// 创建对应 Font 类型的字体 QFont。
virtual const QFont *font(Font type = SystemFont) const;
QStyle
Qt 是跨平台的一个类库,其中 GUI 的控件,在不同的操作系统下都会有着相同的显示效果,这就是就是默认样式。 QApplication::style() 方法可以获取应用当前的样式。Qt 内置的界面的控件都是使用 QStyle
来进行绘画的,来确保它们在所运行平台的样式效果一致。
QStyle 主要负责控制 QtWidgets 应用的大部分样式。Qt 内置了多种风格,其中 windows
样式和 fusion
样式默认可用,而有些样式需在特定平台上才可用,比如 windowsxp 样式、gtk 样式等。每当应用程序需要渲染某些控件(如按钮)时,Qt 都会将这一工作交给样式插件来完成。有些样式(如 Windows)会使用平台原生接口来渲染部件,而有的则会从头开始绘制部件,其实现可以完全自定义,以便完全控件控制外观。
三种方法来重新定义Qt内置窗口部件的外观:
- 子类化QStyle或者一个预定义的风格,这种方法很好用,Qt本身就是用这种方法为它所支持的不同平台提供基于平台的外观的
- 子类化个别的窗口部件类,并且重新实现它的绘制和鼠标事件处理器。
- Qt 样式表
子类化 QStyle 实现自定义样式有两种方法:静态方法和动态方法。
- 静态方法:也就是继承已经存在的类,不是QStyle,通常是QCommonStyle或者是QFusionStyle 等等。然后实现其中的虚函数,重写自己需要修改的部分代码。通常选择和自己所期望的最相近的类来继承。
- 动态方法:在动态方法中,可以在运行时修改系统样式的行为。QProxyStyle 中描述了动态方法。
QStyle 是利用插件机制来实现的。若要开发一款自定义样式,只需要继承 QStyle 或其子类,并重写相关方法即可。
QStyle 类继承关系:
QStyle
QCommonStyle
QFusionStyle
QProxyStyle
由上面的继承关系可知,要实现自定义样式一般有三种继承方式:
- 继承于 QProxyStyle :
- 优点:QProxyStyle 是 QCommonStyle 的子类,用于在现有默认样式基础上自定义样式。
- 继承于 QCommonStyle :
- 优点一:QStyle 的子类,其封装了 GUI 程序中的通用的一些通用的外观。
- 优点二:接口很全面,不需要写过多地代码(相对直接继承 QStyle 而言)。
- 优点二:可以跨平台使用该风格(相对直接继承于 QMacStyle)。
- 继承于 QStyle :
- 说明:QStyle 是一个纯虚抽象基类。是所有样式的源头。
- 缺点:里面虚函数超级多,重写该类就需要我们一个个实现它们,所需工作量非常大。
QCommonStyle 类实现了 GUI 控件的共同界面外观, 因此该类实现的界面并不一定完整,而 QProxyStyle 类则实现了一个 QStyle (通常是默认 的系统样式),因此该类的实现比较完整。
如下为需要经常重写的方法说明:
// 常用 QStyle interface
public:
// 用于初始化部件的外观,会在部件创建完成之后,在第一次显示之前被调用,默认实现什么也不做。
// 子类化 QStyle 时,可利用该函数的调用时机,对部件的一些属性进行初始化。
// 通常在此函数内指定配色方案,也即配置调色板,改变调色板为样式指定的颜色调色板。
void polish(QWidget *widget);
// 作用和 polish() 相似,但只有在部件被销毁时才会被调用。
// 总结:当样式应用到窗口部件时,polish(QWidget*)就会调用,从而允许我们进行最后的定制,当动态改变样式的时候,unpolish就会调用,来撤销polish的影响。
void unpolish(QWidget *widget);
// 设置某一元素的控件的行为,或开启选择指定的某种特性
int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const;
// drawPrimitive(),drawControl(),drawComplexControl()具体的执行绘制。
// 绘画 GUI 某一控件的某一部分(原始元素),通常为详细绘画;
void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
// 绘画 GUI 某一控件的某一部分(控制元素)
void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
// 返回某一个元素的矩形大小;由样式选项 option 所描述的控件的子元素 subElement 的矩形;
QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const;
// 绘画 GUI 某一复杂控件的元素,将该控件的每一个部分都绘画分派出去,调用 drawControl() 里面对应的枚举。
// 相当于 drawControl() 的上一层
void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const;
// 子控件 subControl 的矩形
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
// 返回某个元素的长度(像素值),来影响窗口部件的绘制
int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const;
// 某一个元素的矩形大小;由样式选项 option 所描述的控件的子元素 subElement 的矩形;
QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w) const;
大多数QStyle绘制函数有四个参数:
- 枚举值,指定要绘制哪个图形元素。这个 enum 在不同的 draw 函数中可能是不一样的。例如,在 drawControl() 中是 QStyle::ControlElement,指的是组件;在 drawPrimitive() 中则是 QStyle::PrimitiveElement,指的是组件的原始组成元素,例如焦点框,check box 的小勾等;
- QStyleOption 对象指针,这个对象保存了 painter 绘制时所需要的所有数据信息,比如绘制大小、坐标、绘制文本等。不同的 element 可能对应着不同的 QStyleOption 的子类,这个在文档中可以找到;
- QPainter 对象指针,系统即用这个 painter 进行绘制
- QWidget 对象指针(可选),用于辅助绘制。
QStyle从QStyleOption中获取呈现图形元素所需的所有信息。如果样式需要小部件执行特殊效果,则将小部件作为最后一个参数传递,但这不是必须的。实际上,通过正确设置QPainter,可以使用QStyle来绘制任何绘图设备,而不仅仅是控件。
在自定义样式过程中,对一个个方法的重写相当于是对某个UI元素设置样式,而UI元素则是 QStyle 中定义的 N 个枚举。同时这些方法也按照UI元素的类型作了分类。
QStyleOption
QStyleOption and its subclasses contain all the information that QStyle functions need to draw a graphical element.
QStyleOption类存储QStyle函数使用的参数。QStyleOption及其子类包含了QStyle函数绘制图形元素所需的所有信息。
QStyleOption是风格的设置类,定义了最基本的绘制控件所需的信息。
绘制不同控件时,控件所使用的设置类继承QStyleOption,且OptionType值不同。
如绘制按钮的风格设置类QStyleOptionButton继承QStyleOption时,Type = SO_Button表明是要绘制按钮,且添加了一些按钮才有的属性。
QPalette
QPalette 是一组用于绘制用户界面元素的颜色。调色板由 QPA 定义。
在绘制控件时,所选的 QStyle 可能会(也可能不会)使用该调色板。应用程序开发时也可以手动从调色板中查询颜色,以绘制自定义窗口小部件,仍然遵循平台所需的颜色。
平台主题可以只提供一个调色板,也可以包含浅色和深色,或者允许用户配置任意颜色集(在 KDE Plasma 桌面环境中就可以自定义配置颜色)。在某个具体的应用中也可以覆盖系统提供的调色板,例如提供应用内独立的黑暗模式切换。
QPalette 中的 ColorGroup and ColorRole.
...
// Do not change the order, the serialization format depends on it
enum ColorGroup { Active, Disabled, Inactive, NColorGroups, Current, All, Normal = Active };
Q_ENUM(ColorGroup)
enum ColorRole { WindowText, Button, Light, Midlight, Dark, Mid,
Text, BrightText, ButtonText, Base, Window, Shadow,
Highlight, HighlightedText,
Link, LinkVisited,
AlternateBase,
NoRole,
ToolTipBase, ToolTipText,
NColorRoles = ToolTipText + 1,
Foreground = WindowText, Background = Window
};
Q_ENUM(ColorRole)
...
QPainter
The QPainter class performs low-level painting on widgets and other paint devices.
QPainter provides highly optimized functions to do most of the drawing GUI programs require. It can draw everything from simple lines to complex shapes like pies and chords. It can also draw aligned text and pixmaps. Normally, it draws in a "natural" coordinate system, but it can also do view and world transformation. QPainter can operate on any object that inherits the QPaintDevice class.
The common use of QPainter is inside a widget's paint event: Construct and customize (e.g. set the pen or the brush) the painter. Then draw. Remember to destroy the QPainter object after drawing. For example:
void SimpleExampleWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::blue);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
The core functionality of QPainter is drawing, but the class also provide several functions that allows you to customize QPainter's settings and its rendering quality, and others that enable clipping. In addition you can control how different shapes are merged together by specifying the painter's composition mode.
疑问
QStyle 与 QPlatformTheme 各自的作用是什么?
QStyle和QPlatformTheme都是Qt框架中用于处理应用程序的外观和风格的类,但它们的作用和使用场景有所不同。
QStyle是一个抽象基类,它定义了一组用于渲染图形用户界面元素(如按钮、滑块、复选框等)的方法。每种具体的QStyle子类都提供了一种特定的外观和感觉,例如QFusionStyle提供了一种跨平台的风格,而QWindowsStyle则模仿了Windows的风格。开发者可以通过继承QStyle并重写其方法来创建自定义的风格。
QPlatformTheme则是Qt的平台抽象层的一部分,它提供了一种机制来定制和控制在特定平台上运行的Qt应用程序的外观和行为。例如,它可以用于获取和设置系统级别的字体和颜色方案,或者创建和使用平台特定的对话框和图标。QPlatformTheme的实例通常由Qt的平台插件创建和管理,而不是由应用程序开发者直接使用。
总结:QStyle主要用于定制和控制Qt控件的外观,负责界面样式的绘制;而QPlatformTheme则用于封装了一些特有的行为,与底层平台进行交互。
实践
参考官方示例程序 Style Plugin Example - Qt5 学习如何创建自定义样式插件并应用,进一步学习其中涉及到的概念。
自定义的主题依赖是QT的插件机制(Style Plugin Example
属于扩展 Qt 本身的插件)。自定义主题会以动态库的形式加载到Qt应用程序中。因此创建自定义主题的步骤与创建插件的步骤类似:
- 创建一个样式插件。
- 继承一个样式的基础类或子类,重写相关函数,自定义具体的样式。
- 编译安装插件库。
- 在所需应用中注册自定义主题。
示例 demo : SimpleStylePlugin
1. 创建一个样式插件
// SimpleStylePlugin.h
#ifndef SIMPLESTYLEPLUGIN_H
#define SIMPLESTYLEPLUGIN_H
#include <QStylePlugin>
class SimpleStylePlugin : public QStylePlugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "../simplestyle.json") // 使用 Q_PLUGIN_METADATA() 宏导出插件。
public:
SimpleStylePlugin() = default;
QStringList keys() const;
QStyle *create(const QString &key) override;
};
#endif //SIMPLESTYLEPLUGIN_H
keys()函数返回这个style的名称,create()函数的一个参数为string类型,返回QStyle的键值。这两个函数都是QStylePlugin中继承下来的纯虚函数,当应用程序需要创建一个SimpleStyle风格的实例时,插件将会被创建。
// SimpleStylePlugin.cpp
#include "SimpleStylePlugin.h"
#include "SimpleStyle.h"
QStringList SimpleStylePlugin::keys() const {
return {"SimpleStyle"};
}
QStyle* SimpleStylePlugin::create(const QString&key) {
if (key.toLower() == "simplestyle") {
return new SimpleStyle;
}
return nullptr;
}
2. 继承一个样式的基础类或子类,自定义具体的样式
// SimpleStyle.h
#ifndef SIMPLESTYLE_H
#define SIMPLESTYLE_H
#include <QProxyStyle>
class SimpleStyle : public QProxyStyle
{
Q_OBJECT
public:
SimpleStyle() = default;
void polish(QPalette &palette) override;
};
#endif //SIMPLESTYLE_H
// SimpleStyle.cpp
#include "SimpleStyle.h"
void SimpleStyle::polish(QPalette &palette)
{
palette.setBrush(QPalette::Button, Qt::red);
}
3. 编译安装插件库
将插件编译为动态库 *.so
文件放入 QT 插件路径:
例如我系统下为:
$ /usr/lib64/qt5/plugins/styles/
4. 在所需应用中注册自定义主题
int main(int argc, char* argv[]) {
QApplication app(argv, args);
QApplication::setStyle(QStyleFactory::create("simplestyle"));
// ...
return app.exec();
}
当QApplication对象被初始化后,Qt将加载style插件。QStyleFactory类能识别所有style并且能生成对象。
Ref
QT 插件解惑:【翻译 + 整理】如何创建Qt插件 - 友善啊,朋友 - CSDN
解释了几个入门概念: How platform integration in Qt/KDE apps works - Nico's blog
较全的概念性文章:让你的 Qt 桌面程序看上去更加 native(三):自定义 style
QStyle 解析:QStyle设置界面的外观和QCommonStyle继承关系图讲解和使用
给了一个实例分析:Qt学习笔记外观篇(一)~(四)
UKUI 主题框架实战技巧
Qt 插件机制
Qt主题原理