目录:
摘要
本文主要记录如何实现使用dll插件在主窗体中添加工具条。
如何创建Qt插件
Qt提供了两套API创建插件:
- 高级API —— 扩展Qt自身自定义数据库驱动、图片格式、文字编码、自定义样式等;
- 底层API —— 扩展Qt编写的应用程序。
高级API:
以后有机会碰到再写
底层API:
不仅Qt可以通过插件扩展自身,Qt应用程序也可以。应用程序须通过QPluginLoader去检测和加载插件。如此一来,插件就可以提供不限于数据库驱动、图片格式、文字编码、自定义样式等功能。
通过插件来扩展应用程序涉及以下步骤:
1. 定义一系列的接口(只有纯虚函数的类)与插件进行交互;
2. 使用宏Q_DECLARE_INTERFACE()告诉Qt的meta-object system关于插件的信息;
3. 在应用程序中使用QPluginLoader加载插件;
4. 使用qobject_cast()测试插件是否实现了指定的接口。
例如,下面有一个接口类的定义:
class FilterInterface
{
public:
virtual ~FilterInterface() {}
virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent) = 0;
};
来自 http://doc.qt.io/qt-5/plugins-howto.html
下面是实现该接口插件的定义:
#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);
};
来自 http://doc.qt.io/qt-5/plugins-howto.html
简单代码实现
完整代码参见我的Github:GEO-LIKE
1.定义接口
创建一个头文件,类名为ShapePluginInterface
,类型为shared library
。接口类中所有函数都必须为纯虚函数,且接口设计要包含所有主函数中要用的功能。
#include <QObject>
#include <QString>
#include <QtWidgets/QMainWindow>
class ShapePluginInterface{
public:
virtual ~ShapePluginInterface(){}
virtual QString description() = 0;
virtual void generateShape(QString shapeType) = 0;
virtual void initialization(QMainWindow* window) = 0;
};
#define SHAPEPLUGININTERFACE_IID "com.geolike.shapeplugininterface"
Q_DECLARE_INTERFACE(ShapePluginInterface, SHAPEPLUGININTERFACE_IID)
接口文件中显式定义了4个虚函数,都必须实现。
~ShapePluginInterface()
QString description()
对插件的简单描述,主程序可以通过该函数读取一些信息。void generateShape(QString shapeType)
主要功能函数,输入为一个字符串。可以定义其他的输出类型,如
QPixmap filterImage(QPixmap img);
void initialization(QMainWindow* window)
初始化插件,调用该函数在主窗体创建工具条。
关于接口的一些知识:
#define SHAPEPLUGININTERFACE_IID "com.geolike.shapeplugininterface"
Q_DECLARE_INTERFACE(ShapePluginInterface, SHAPEPLUGININTERFACE_IID)
定义了一个常量SHAPEPLUGININTERFACE_IID
并将其声明为接口的ID。关于这些知识,这里介绍的很详细
2. 实现接口的插件类
在Qt Creator中创建一个C++ Library,类名为BasicShapePlugin
//basicshapeplugin.h
#include "basicshapeplugin_global.h"
#include "shapeplugininterface.h" //要包含接口文件
··· ···
class BASICSHAPEPLUGINSHARED_EXPORT BasicShapePlugin:
public QObject,
public ShapePluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.geolike.shapeplugininterface")
Q_INTERFACES(ShapePluginInterface)
public:
BasicShapePlugin();
~BasicShapePlugin();
QString description();
void generateShape(QString shapeType);
void initialization(QMainWindow* window);
public slots:
void on_Action_triggered();
};
要点:
- 插件要使用宏
Q_OBJECT
声明为QObject
对象 Q_PLUGIN_METADATA
声明接口IDQ_INTERFACES(ShapePluginInterface)
声明接口
一个接口文件中可能定义了许多个接口,一个接口文件貌似只有一个IID。(参考Qt官方示例Plug & Paint)
最后我只定义了一个槽函数void on_Action_triggered()
该函数用来响应工具条中的Actions。
//basicshapeplugin.cpp
#include "basicshapeplugin.h"
BasicShapePlugin::BasicShapePlugin(){}
BasicShapePlugin::~BasicShapePlugin(){}
QString BasicShapePlugin::description()
{
return "This is a plugin to generate shapes.";
}
void BasicShapePlugin::generateShape(QString shapeType)
{
qDebug() << shapeType;
}
void BasicShapePlugin::on_Action_triggered()
{
qDebug() << "trigered!";
generateShape("Action Triggered!");
}
void BasicShapePlugin::initialization(QMainWindow *window)
{
auto *toolBar = new QToolBar;
auto *addShapeAction = toolBar->addAction("Shapes");
addShapeAction->setToolTip("Return a shape");
connect(addShapeAction, &QAction::triggered, this, &BasicShapePlugin::on_Action_triggered);
window->addToolBar(toolBar);
}
要点:
generateShape(QString shapeType)
直接打印输入的字符串initialization(QMainWindow *window)
最重要的一个函数,输入主窗体的指针*window
,然后使用下面语句,将tooBar添加到主窗体中。
window->addToolBar(toolBar);
- 在
initialization(QMainWindow *window)
定义了一个ActionaddShapeAction
,这里注意Action的创建方法,是toolBar->addAction(QString)的返回值,之前尝试过直接new QAction,但是一直报信号和接口参数不匹配,在StackOverFlow请教别人之后,得到了可行的方法:
auto *toolBar = new QToolBar;
auto *addShapeAction = toolBar->addAction("Shapes");
on_Action_triggered()
工具条中对应的动作被触发时,输出一些信息。槽函数的参数列表是可以比信号少的,上面提问的时候也有人跟我纠结这个问题。参考Qt 学习之路 2(16):深入 Qt5 信号槽新语法
3. 主窗体调用
//mainwindow.h
#include "shapeplugininterface.h"
··· ···
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
··· ···
private:
void getPluginList();
Ui::MainWindow *ui;
};
这里定义了一个 getPluginList()
来加载插件
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#define PLUGIN_SUBFOLDER "/plugins/"
··· ···
void MainWindow::getPluginList()
{
QDir pluginsDir(qApp->applicationDirPath() + PLUGIN_SUBFOLDER);
QFileInfoList plugins = pluginsDir.entryInfoList( QDir::NoDotAndDotDot | QDir::Files, QDir::Name);
foreach (QFileInfo plugin, plugins) {
if(QLibrary::isLibrary(plugin.absoluteFilePath())){
QPluginLoader pluginLoader(plugin.absoluteFilePath(),this);
if(dynamic_cast<ShapePluginInterface*>(pluginLoader.instance())) {
ui->pluginList->addItem(plugin.fileName());
ShapePluginInterface *shape_plugin =
dynamic_cast<ShapePluginInterface*>(pluginLoader.instance());
shape_plugin->initialization(this);
// pluginLoader.unload();
emit pluginLoaded();
}
else
{//do somthing}
}
else
{ //some warrings }
if(ui->pluginList->count() <= 0)
{ //some warrings }
}
}
使用下面的语句调用,最重要的还是shape_plugin->initialization(this)
ShapePluginInterface *shape_plugin =dynamic_cast<ShapePluginInterface*>(pluginLoader.instance());
shape_plugin->initialization(this);