缘由
最近在用Qt做项目,在网上找插件编写的资料,没有完整的代码,要下载的资源都被传到需要积分的网站上了,感觉很不爽。因此把插件示例项目编写完整,并在github上开了一个qtDemo项目,写了这篇文章。
作为一个拖砖项目,望大家在学习同时,不要忘记了分享的精神。这个项目我会把学习Qt的代码不断更新上来,若有同道者,请pull request给我,本项目收集Qt示例程序,谢谢!
技术选择
我的项目最低支持msvc 10.0 x86,g++ 4.8.5,Qt5.5.1,因为需要跨平台,项目管理使用cmake(最低支持3.10),现在同时支持windows和linux,IDE我使用qcreator,在windows下你也可以选择visual studio 2019。
Cmake介绍
使用cmake主要是因为它的跨平台性好,可以脱离操作系统与IDE的束缚,而且已经被广泛支持了。
设置cmake所需最低版本号
cmake_minimum_required(VERSION 3.10)
项目及版本号,项目名称可以通过${PROJECT_NAME}取得
project(plg1 VERSION 0.1.0)
将当前目录加入文件包含搜索目录,若不设置,vs2019与qcreator的当前目录会不兼容。
set(CMAKE_INCLUDE_CURRENT_DIR ON)
打开全局moc与全局uic
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
查找系统中已安装的Qt版本,需要的库。
最好每一个库都要写,Qt也会根据依赖关系自动添加
find_package(Qt5 REQUIRED Widgets)
find_package(Qt5Widgets)
find_package(Qt5Core)
find_package(Qt5Gui)
需要建立名称为QTDIR的系统环境变量,指定安装的Qt目录。
收集我的们源文件,这有很多方法,大家可以去了解并使用自己喜欢的方式。
FILE(GLOB SRC_FILES "*.cpp" "*.h" "*.ui")
创建工程文件
add_executable(${PROJECT_NAME} ${SRC_FILES}) #可执行文件创建方式
add_library(${PROJECT_NAME} SHARED ${SRC_FILES}) #动态链接库创建方式
添加子项目,也就是我们的插件
add_subdirectory(sub1)
add_subdirectory(sub2)
添加Qt5依赖项
target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::Core Qt5::Gui)
Qt5插件
项目结构
本示例项目包括三个工程,一个主工程,两插件工程。cmake的项目是以目录为基础的,每个工程的目录下会有一个cmakelists.txt工程文件。
插件基类
主工程中的plugindemoplugin.h是所有插件的基类,我们的每个插件都继承自这个类,它做了插件基础声明及我们的插件能做的行为。本示例每一个插件会给主程序返回一个widget作为centerWidget显示,并提供一个name和information的查询接口,提供插件必要的信息。
class QtPluginDemoInterface
{
public:
virtual ~QtPluginDemoInterface() {}
virtual QString name() = 0;
virtual QString information() = 0;
virtual QWidget *centerWidget() = 0; //返回一个Widget设置到centerwidget中进行显示
};
//s声明接口
#define PluginDemoInterface_iid "com.Interface.MainInterface"
Q_DECLARE_INTERFACE(QtPluginDemoInterface, PluginDemoInterface_iid)
插件定义
我们这只对sub1进行一下说明,sub2是类似的,请自行阅读代码。
子项目的工程文件(cmakelists.txt)与主项目的主要的差别是一个是创建可执行文件,一个是创建动态链接库。
头文件plugindemo.h :
#include "../plugindemoplugin.h"
class pluginDemo : public QObject, QtPluginDemoInterface
{
Q_OBJECT //Qt类的标识宏,初学Qt的小伙伴要注意,这行是Qt类必须的
Q_INTERFACES(QtPluginDemoInterface) //这两行声明是插件要求的
Q_PLUGIN_METADATA(IID PluginDemoInterface_iid)
public:
pluginDemo(){};
... //方法声明,省略
};
源文件plugindemo.cpp :
QWidget *pluginDemo::centerWidget()
{
auto btn = new QPushButton("One"); //我们返回一个按钮,作为简单的widget示例
return btn;
}
... //其它的省略了,只是固定信息返回
主程序中加载插件
mainwindow.h定义:
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
int MainWindow::loadPlugins();
void MainWindow::populateMenus(QObject * pluginInterface,QtPluginDemoInterface*i );
void MainWindow::slt_WidgetActionTriggered();
private:
Ui::MainWindow *ui;
};
通过loadPlugins函数加载插件:
int MainWindow::loadPlugins()
{
QDir pluginsDir = QDir(QCoreApplication::applicationDirPath()); //这里要注意路径需要配置好,把子工程的输出目录配置到主工程
if(!pluginsDir.cd("plugins")) return -1; //下的plugins目录中
foreach (QString fileName, pluginsDir.entryList(QDir::Files))
{
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if(plugin)
{
auto centerInterface = qobject_cast<QtPluginDemoInterface*>(plugin);
if(centerInterface)
{
populateMenus(plugin,centerInterface); //将插件作为菜单中的一项
}
}
}
return count;
}
生成菜单函数:
void MainWindow::populateMenus(QObject * pluginInterface,QtPluginDemoInterface*i )
{
static auto menu = menuBar()->addMenu("widgets"); //建立一个菜单项
auto act = new QAction(i->name(),pluginInterface); //建立action,取得的插件对象被绑定在其上
connect(act,&QAction::triggered,this,&MainWindow::slt_WidgetActionTriggered); //事件链接
menu->addAction(act);
}
菜单点击事件响应:
void MainWindow::slt_WidgetActionTriggered()
{
QtPluginDemoInterface * plg = qobject_cast<QtPluginDemoInterface*>(sender()->parent()); //取得插件对象
auto centerWidget = plg->centerWidget(); //取得插件中返回的widget
//我们返回的widget其实是QPushButton,用其配置信息为其设置显示内容
(qobject_cast<QPushButton*>(centerWidget))->setText(plg->information());
setCentralWidget(centerWidget);
}
项目地址
https://github.com/zhoutk/qtDemo
命令行编译
git clone https://github.com/zhoutk/qtDemo
cd qtDemo/plugin & mkdir build & cd build
cmake ..
cmake --build .
编译时注意:cmake默认为x86架构,需要与你安装的Qt版本对应;编译好了,运行前,请注意目录结构是否正确。
小结
抛砖引玉,不吝赐教,谢谢阅读!