Qt 提供了两个用于创建插件的 API:
- 用于编写 Qt 本身扩展的高级 API:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等。
- 用于扩展 Qt 应用程序的低级 API。
一、高级 API:编写 Qt 扩展插件
编写扩展 Qt 本身的插件步骤:
- 继承适当的插件基类
- 实现一些功能
- 添加一个对应的宏
派生插件默认存储在标准插件目录的子目录中。如果插件未存储在适当的目录中,Qt 将找不到它们。
下面是公有的插件基类:
- QImageIOPlugin:目录:imageformats。GUI模块。
- QSqlDriverPlugin:目录:sqldrivers。SQL模块。
- QIconEnginePlugin:目录:iconengines。SVG模块。
- QAccessiblePlugin:目录:accessible。Widgets模块。
- QStylePlugin:目录:styles。Widgets模块。
这个目录指的是可执行文件目录下的子目录。如使用 deployqt 命令打包的时候,会根据工程添加的模块生成相应的子目录。
程序会到相应的目录寻找插件。
官方demo:Style Plugin Example演示了编写扩展 Qt 本身的插件步骤。
simplestyle.h
#include <QProxyStyle>
class QPalette;
class SimpleStyle : public QProxyStyle
{
Q_OBJECT
public:
SimpleStyle() {};
void polish(QPalette &palette) override
{
palette.setBrush(QPalette::Window, Qt::red);
}
};
simplestyleplugin.h
#include <QStylePlugin>
#include "simplestyle.h"
class QStyle;//自定义风格插件
class SimpleStylePlugin : public QStylePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "simplestyle.json")public:
SimpleStylePlugin() {}QStringList keys() const
{
return QStringList() << "SimpleStyle";
}
QStyle *create(const QString &key) override
{
if (key.toLower() == tr("simplestyle"))
{
return new SimpleStyle;
}
return 0;
}
};
将插件的生成目录设置成可执行文件的子目录,可直接使用而不用创建对象。
#include <QtWidgets>
int main(int argv, char *args[])
{
QApplication app(argv, args);
QApplication::setStyle(QStyleFactory::create("simplestyle"));
QWidget w;
QPushButton *styledButton = new QPushButton("这是一个按钮");
QGridLayout *layout = new QGridLayout;
layout->addWidget(styledButton);
QGroupBox *styleBox = new QGroupBox("这是一个部件容器");
styleBox->setLayout(layout);
QGridLayout *outerLayout = new QGridLayout;
outerLayout->addWidget(styleBox, 0, 0);
w.setLayout(outerLayout);
w.setWindowTitle("风格插件例子");
w.show();
return app.exec();
}
把生成的插件文件放到Qt安装目录相应的目录下(比如mingw编译器放到:mingw81_64\plugins\styles),则在其他Qt程序就可以使用此插件,使用方法同上。这就是“Qt拓展功能插件”的含义。
二、低级 API:扩展 Qt 应用程序
不仅 Qt 本身,Qt 应用程序也可以通过插件进行扩展。这需要应用程序使用 QPluginLoader 检测和加载插件。在这种情况下,插件可以提供任意功能。
通过插件使应用程序可扩展涉及以下步骤:
- 定义一组用于与插件通信的接口(只有纯虚函数的类)。
- 使用 Q_DECLARE_INTERFACE() 宏来告诉 Qt 的元对象系统有关接口的信息。
- 在应用程序中使用 QPluginLoader 加载插件。
- 使用 qobject_cast() 来测试插件是否实现了给定的接口。
编写插件包括以下步骤:
- 声明一个从 QObject 和插件想要提供的接口继承的插件类。
- 使用 Q_INTERFACES() 宏来告诉 Qt 的元对象系统有关接口的信息。
- 使用 Q_PLUGIN_METADATA() 宏导出插件。
- 使用合适的 .pro 文件构建插件。
例如,下面是一个接口类的定义:
class FilterInterface
{
public:
virtual ~FilterInterface() {}
virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &image,QWidget *parent) = 0;
};
这是实现该接口的插件类的定义:
#include <QObject>
#include <QtPlugin>
#include <QStringList>
#include <QImage>
#include <plugandpaint/interfaces.h>class ExtraFiltersPlugin : public QObject, public FilterInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" FILE "extrafilters.json")
Q_INTERFACES(FilterInterface)public:
QStringList filters() const;
QImage filterImage(const QString &filter, const QImage &image,QWidget *parent);
};
使用插件时,用 QPluginLoader 载入插件并转成 ExtraFiltersPlugin 对象,即可像正常调用对象的成员函数一样调用插件中的方法。
QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
QObject * pluginObject = loader.instance();
if (pluginObject)
{
ExtraFiltersPlugin * myPlugin= qobject_cast<ExtraFiltersPlugin *>(pluginObject);
QStringList list = myPlugin->filters();
}
在加载插件之前,必须先初始化 QCoreApplication。
三、插件存放位置
Qt 应用程序自动知道哪些插件可用,因为插件存储在标准插件子目录中。应用程序不需要任何代码来查找和加载插件,因为 Qt 会自动处理它们。
在开发过程中,插件的目录是 QTDIR/plugins(其中 QTDIR 是安装 Qt 的目录),每种类型的插件都在该类型的子目录中。如果希望应用程序使用插件并且不想使用标准插件路径,应用程序可以使用自定义路径调用 QCoreApplication::addLibraryPath() 。
注意,路径的最后部分(例如:/styles)不能更改。
如果您分发 Qt 附带的插件(即位于 QTDIR/plugins 目录中的插件),必须将插件所在的 plugins 下的子目录复制到您的应用程序根文件夹(即,不包括 plugins 目录)(这一步 deployqt 命令会自动执行,不用自己动手)。
四、静态插件
将插件包含在应用程序中的正常且最灵活的方法是将其编译为单独提供的动态库,并在运行时进行检测和加载。
插件也可以静态链接到应用程序中。使用静态插件使部署不易出错,但缺点是,如果不完全重新构建和重新发布应用程序,就无法添加插件的功能。
可以按照以下步骤创建自己的静态插件:
- 插件的 .pro 文件中添加 CONFIG += static。
- 在应用程序中使用 Q_IMPORT_PLUGIN() 宏。
- 如果插件提供 qrc 文件,则在应用程序中使用 Q_INIT_RESOURCE() 宏。
- 使用 .pro 文件中的 LIBS 将应用程序与插件库文件链接起来。