CTK框架 - 通信 - 事件注册监听
我们在第一篇中链接的教程是有编译CTK的事件的,编译完成之后会有对应的
liborg_commontk_eventadmin.dll
liborg_commontk_metatype.dll
liborg_commontk_configadmin.dll
liborg_commontk_log.dll
在将cmake中的文件拷贝部分修改如下, 如果你拉取了gitee中的代码,则不需修改这个部分,因为我已经在前面加上了这个部分:
if (CMAKE_BUILD_TYPE STREQUAL "Debug") file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/CTKCore.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/CTKPluginFramework.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/plugins/liborg_commontk_eventadmin.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/plugins/liborg_commontk_metatype.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/plugins/liborg_commontk_configadmin.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/libd/ctk-0.1/plugins/liborg_commontk_log.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) else () file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/CTKCore.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/CTKPluginFramework.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/plugins/liborg_commontk_eventadmin.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/plugins/liborg_commontk_metatype.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/plugins/liborg_commontk_configadmin.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) file(COPY "${PROJECT_SOURCE_DIR}/third_party/CTK/lib/ctk-0.1/plugins/liborg_commontk_log.dll" DESTINATION ${PROJECT_SOURCE_DIR}/output/lib/plugins) endif ()
事件介绍
CTK框架中的事件监听,即观察者模式流程上是这样:接收者注册监听事件->发送者发送事件->接收者接收到事件并响应;相比调用插件接口,监听事件插件间依赖关系更弱,不用指定事件的接收方和发送方是谁。比如我们需要弹出一个界面,可以使用事件来弹出。
● 通信主要使用了ctkEventAdmin结构体,主要定义了如下接口:
接口名称 | 作用 |
postEvent | 类通信形式,异步发送事件 |
sendEvent | 类通信形式,同步发送事件 |
publishSignal | 信号槽通信形式,发送事件 |
unpublishSignal | 信号槽通信形式, 取消发送事件 |
subscribeSlot | 信号槽通信形式, 订阅事件, 返回订阅ID |
unsubscribeSlot | 信号槽通信形式,取消订阅事件 |
updateProperties | 更新某个订阅主题 |
- 通信数据是ctkDictionary
ctkDictionary 的原型是 typedef QHash<QString,QVariant> ctkDictionary;
我们之前再main.cpp中使用了如下代码:
// 在插件的搜索路径列表中添加一条路径 ctkPluginFrameworkLauncher::addSearchPath(path + "/libs/plugins"); // 设置并启动 CTK 插件框架 try { ctkPluginFrameworkLauncher::start("org.commontk.eventadmin"); } catch (ctkException e) { std::cout << e.message().toStdString() << std::endl; }
这个里面的的ctkPluginFrameworkLauncher::addSearchPath是额外添加搜索路径,然后启用org.commontk.eventadmin事件插件,如果想使用事件则必须启动这个插件。
代码示例
第一步:
我们新增一个插件about,按照步骤写出activator和plugin,其他的配置不变
aboutactivator.h
#ifndef CTKTEST_ABOUTACTIVATOR_H #define CTKTEST_ABOUTACTIVATOR_H #include <QObject> #include "ctkPluginActivator.h" class AboutPlugin; class AboutActivator : public QObject, public ctkPluginActivator { Q_OBJECT Q_PLUGIN_METADATA(IID "About") Q_INTERFACES(ctkPluginActivator) public: AboutActivator(); void start(ctkPluginContext *context); void stop(ctkPluginContext *context); private: AboutPlugin *plugin_{nullptr}; }; #endif //CTKTEST_ABOUTACTIVATOR_H
aboutactivator.cpp
#include "aboutactivator.h" #include "aboutplugin.h" #include <iostream> AboutActivator::AboutActivator() { } void AboutActivator::start(ctkPluginContext *context) { std::cout << "about start" << std::endl; plugin_ = new AboutPlugin(context); } void AboutActivator::stop(ctkPluginContext *context) { std::cout << "about stop" << std::endl; if(plugin_) { delete plugin_; plugin_ = nullptr; } }
aboutplugin.h
#ifndef CTKTEST_ABOUTPLUGIN_H #define CTKTEST_ABOUTPLUGIN_H #include <QObject> #include "ctkPluginContext.h" #include "service/event/ctkEventAdmin.h" #include "service/event/ctkEventHandler.h" class AboutPlugin : public QObject, public ctkEventHandler { Q_OBJECT Q_INTERFACES(ctkEventHandler) public: explicit AboutPlugin(ctkPluginContext *context); protected: void handleEvent(const ctkEvent& event) override; private: ctkPluginContext *m_context; }; #endif //CTKTEST_ABOUTPLUGIN_H
aboutplugin.cpp
#include "aboutplugin.h" #include <service/event/ctkEventConstants.h> #include <iostream> AboutPlugin::AboutPlugin(ctkPluginContext *context) : m_context( context ) { //注册监听信号"About" ctkDictionary dic; dic.insert(ctkEventConstants::EVENT_TOPIC, "About"); m_context->registerService<ctkEventHandler>(this, dic); } void AboutPlugin::handleEvent(const ctkEvent &event) { //接收监听事件接口 if(event.getTopic() == "About") { std::cout << event.getProperty("About").toString().toStdString() << std::endl; } }
我们将about继承自ctkEventHandler
是为了可以注册ctkEventHandler
服务,方便接收发过来的事件
这里我们需要重新实现handleEvent
函数来处理接收到的事件的事件。
第二步:
我们需要在imainwindow中添加一个函数来在core插件中实现发送事件,并且在Core中实现该函数
imainwindow.h
#ifndef CTKTEST_IMAINWINDOW_H #define CTKTEST_IMAINWINDOW_H #include <QObject> class iMainWindow { public: virtual ~iMainWindow() = default; virtual void popWindow() = 0; virtual void senEvent() = 0; }; //此宏将当前这个接口类声明为接口,后面的一长串就是这个接口的唯一标识。 Q_DECLARE_INTERFACE(iMainWindow, "interface_mainwindow") #endif //CTKTEST_IMAINWINDOW_H
void MainWindowPlugin::senEvent() { //获取事件服务接口 ctkServiceReference ref = context_->getServiceReference<ctkEventAdmin>(); ctkEventAdmin* eventAdmin{nullptr}; if(ref) { eventAdmin = context_->getService<ctkEventAdmin>(ref); context_->ungetService(ref); } //发送事件 ctkDictionary message; ctkDictionary message2; ctkDictionary message3; message.insert("About", "im a message to about plugin"); message2.insert("About", "im AAA message to about plugin"); message3.insert("MMM", "im MMM message to about plugin"); if(eventAdmin) eventAdmin->postEvent(ctkEvent("About", message)); if(eventAdmin) eventAdmin->postEvent(ctkEvent("AAA", message2)); if(eventAdmin) eventAdmin->postEvent(ctkEvent("About", message3)); }
第三步:
在main中调用该函数
ctkServiceReference ref =pluginContext->getServiceReference<iMainWindow>(); iMainWindow* mainWindow{nullptr}; if(ref) { mainWindow = pluginContext->getService<iMainWindow>(ref); pluginContext->ungetService(ref); } if(mainWindow) { mainWindow->popWindow(); mainWindow->senEvent(); }
测试的时候我们的代码只能打印第一个信息,解释一下ctkEvent的第一个参数时我们注册事件的时候的过滤器,只有满足该过滤条件的时候我们注册的事件才能收到该事件,message的第一个参数时event.getTopic()的值。
信号槽介绍
原理是将Qt自己的信号与CTK的发送事件绑定、槽与事件订阅绑定。
修改mainwidowplugin如下, 不要忘了在头文件中定义信号blogPublished:
mainwindowplugin.cpp
// 在构造函数中绑定信号 MainWindowPlugin::MainWindowPlugin(ctkPluginContext *context) : context_(context) { ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>(); if (ref) { ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref); // 使用 Qt::DirectConnection 等同于 ctkEventAdmin::sendEvent() eventAdmin->publishSignal(this, SIGNAL(blogPublished(ctkDictionary)), "About"); } } // 在sendEvent 发送 void MainWindowPlugin::senEvent() { ctkDictionary props; props.insert("title", "title 1"); props.insert("content", "content 1"); props.insert("author", "author 1"); std::cout << "Publisher sends a message, properties:" << std::endl; emit blogPublished(props); }
修改aboutplugin如下, 不要忘了在头文件中定义信号blogPublished
:
// AboutPlugin.h #include <QObject> #include "ctkPluginContext.h" #include "service/event/ctkEventAdmin.h" #include "service/event/ctkEventHandler.h" class AboutPlugin : public QObject { Q_OBJECT public: explicit AboutPlugin(ctkPluginContext *context); public slots: void onBlogPublished(const ctkEvent& event); private: ctkPluginContext *m_context; }; // AboutPlugin.cpp #include "aboutplugin.h" #include <service/event/ctkEventConstants.h> #include <iostream> AboutPlugin::AboutPlugin(ctkPluginContext *context) : m_context( context ) { //注册监听信号"About" ctkDictionary dic; ctkDictionary props; props[ctkEventConstants::EVENT_TOPIC] = "About"; ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>(); if (ref) { ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref); eventAdmin->subscribeSlot(this, SLOT(onBlogPublished(ctkEvent)), props); } } void AboutPlugin::onBlogPublished(const ctkEvent &event) { QString title = event.getProperty("title").toString(); QString content = event.getProperty("content").toString(); QString author = event.getProperty("author").toString(); std::cout << "EventHandler received the message, topic:" << event.getTopic().toStdString() << "properties:" << "title:" << title.toStdString() << "content:" << content.toStdString() << "author:" << author.toStdString() << std::endl; }
这个时候我们运行,则会打印出对应的EventHandler received the message, topic:Aboutproperties:title:title 1content:content 1author:author 1
事件通信和信号槽通信
1、事件通信
原理就是直接将信息使用CTK的eventAdmin接口send/post出去。
2、信号槽通信
原理是将Qt自己的信号与CTK的发送事件绑定、槽与事件订阅绑定。
二种方式的区别:
1、通过event事件通信,是直接调用CTK的接口,把数据发送到CTK框架;通过信号槽方式,会先在Qt的信号槽机制中转一次,再发送到CTK框架。故效率上来讲,event方式性能高于信号槽方式。
2、两种方式发送数据到CTK框架,这个数据包含:主题+属性。主题就是topic,属性就是ctkDictionary。 一定要注意signal方式的信号定义,参数不能是自定义的,一定要是ctkDictionary,不然会报信号槽参数异常错误。
3、两种方式可以混用,如发送event事件,再通过槽去接收;发送signal事件,再通过event是接收。
4、同步:sendEvent、Qt::DirectConnection;异步:postEvent、Qt::QueuedConnection
这里的同步是指:发送事件之后,订阅了这个主题的数据便会处理数据【handleEvent、slot】,处理的过程是在发送者的线程完成的。可以理解为在发送了某个事件之后,会立即执行所有订阅此事件的回调函数。
异步:发送事件之后,发送者便会返回不管,订阅了此事件的所有插件会根据自己的消息循环,轮到了处理事件后才会去处理。不过如果长时间没处理,CTK也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。