CTK框架 - 将菜单按钮写到插件中
之前我们在的两个插件Core和About,在Core和About中加入了界面,并且Core中插入了一个菜单,点击之后弹出About界面。
我们现在来用之前的知识把菜单改成注册的,并且点击之后弹出About界面。
我这里的思路是使用事件或者是在Core中加注册的服务来注册菜单。之后点击菜单的时候发送事件。
代码示例
1.编写一个menubar 用来管理菜单和处理其他插件注册的menu事件
menubar.h
#ifndef CTKPLUGIN_MENUBAR_H #define CTKPLUGIN_MENUBAR_H #include <QMenuBar> #include "service/event/ctkEventAdmin.h" #include "service/event/ctkEventHandler.h" class MenuBar : public QMenuBar, public ctkEventHandler { Q_OBJECT Q_INTERFACES(ctkEventHandler) public: MenuBar(ctkPluginContext *context, QWidget *parent = nullptr); ~MenuBar() override; void initMenu(); QMenu *getMenu(const QString &name); signals: void sig_insertMenu(const QJsonObject &obj); protected: void handleEvent(const ctkEvent& event) override; protected slots: void insertMenuAndActions(const QJsonObject &obj); void actionTriggered(); private: ctkPluginContext *context_; QStringList menu_descriptions_; QMap<QString, QMenu*> menu_map_; QMap<QString, QList<QAction *>> actions_map_; }; #endif //CTKPLUGIN_MENUBAR_H
menubar.cpp
#include "menubar.h" #include "ctkPluginContext.h" #include "service/event/ctkEventConstants.h" #include <iostream> #include <QJsonObject> #include <QJsonDocument> #include <QAction> MenuBar::MenuBar(ctkPluginContext *context, QWidget *parent) : QMenuBar(parent) , context_(context) { menu_descriptions_ << "File" << "Edit" << "MPI" << "Help" << "Page"; initMenu(); connect(this, &MenuBar::sig_insertMenu, this, &MenuBar::insertMenuAndActions, Qt::QueuedConnection); // 注册监听信号"mainwindow_action" ctkDictionary dic; dic.insert(ctkEventConstants::EVENT_TOPIC, "mainwindow_action"); try { context_->registerService<ctkEventHandler>(this, dic); } catch (ctkException e) { std::cout << e.message().toStdString() << std::endl; } } MenuBar::~MenuBar() { } void MenuBar::initMenu() { for(const auto &name : menu_descriptions_) { QMenu *menu = new QMenu(name,this); menu_map_[name] = menu; addMenu(menu); } } void MenuBar::handleEvent(const ctkEvent &event) { if(event.getTopic() == "mainwindow_action") { QStringList names = event.getPropertyNames(); for(const auto &name : names) { QVariant val = event.getProperty(name); emit sig_insertMenu(val.toJsonObject()); } } } void MenuBar::actionTriggered() { QAction *action = dynamic_cast<QAction *>(sender()); QString text = action->text(); std::cout << text.toStdString() << std::endl; //获取事件服务接口 ctkServiceReference ref; ctkEventAdmin* eventAdmin{nullptr}; ref = context_->getServiceReference<ctkEventAdmin>(); if(ref) { eventAdmin = context_->getService<ctkEventAdmin>(ref); context_->ungetService(ref); } //发送事件 ctkDictionary message; if(eventAdmin) eventAdmin->postEvent(ctkEvent(text, message)); } void MenuBar::insertMenuAndActions(const QJsonObject &obj) { if(obj.isEmpty()) { return; } QString key = obj["menu_name"].toString(); if(menu_map_.contains(key)) { QAction *action = new QAction(obj["action_name"].toString() ,this); action->setIcon(QIcon(obj["picture"].toString())); connect(action, &QAction::triggered, this, &MenuBar::actionTriggered); if(actions_map_.contains(key)) { actions_map_[key].insert(obj["num"].toInt(), action); } else { actions_map_.insert(key, QList<QAction*>() << action); } } else { QMenu *menu = new QMenu(key, this); menu_map_.insert(key, menu); QAction *action = new QAction(obj["action_name"].toString() ,this); action->setIcon(QIcon(obj["picture"].toString())); connect(action, &QAction::triggered, this, &MenuBar::actionTriggered); actions_map_[key].insert(obj["num"].toInt(), action); if(actions_map_.contains(key)) { actions_map_[key].insert(obj["num"].toInt(), action); } else { actions_map_.insert(key, QList<QAction*>() << action); } } menu_map_[key]->clear(); menu_map_[key]->addActions(actions_map_[key]); } QMenu *MenuBar::getMenu(const QString &name) { return menu_map_[name]; }
2.给定注册接口,按照以下的形式去发送注册菜单的事件
actionregister.h
#ifndef CTKPLUGIN_ACTIONREGISTER_H #define CTKPLUGIN_ACTIONREGISTER_H #include "ctkDictionary.h" #include <QJsonObject> /***** * @brief 注册菜单栏 * @param name 是现实在action的菜单menu的名称 * @param id 是该菜单的唯一编号(也是菜单点击后的事件的id, 事件根据此id发送) * @param num 是排在第几个位置(如果有重复的可能会按照后注册插件的来) * @param picture 是图标的路径 */ struct ActionData { QString menu_name; QString action_name; int num{-1}; QString picture; }; class ActionDictionary { public: ActionDictionary() = default; ~ActionDictionary() = default; void registerAction(const ActionData &actionData); ctkDictionary getDictionary() { return dictionary; } private: ctkDictionary dictionary; }; void ActionDictionary::registerAction(const ActionData &actionData) { QJsonObject obj; obj.insert("menu_name", actionData.menu_name); obj.insert("action_name", actionData.action_name); obj.insert("num", actionData.num); obj.insert("picture", actionData.picture); dictionary.insert(actionData.action_name, QVariant::fromValue(obj)); } #endif //CTKPLUGIN_ACTIONREGISTER_H
3.在aboutplugin中发送对应的事件
void AboutPlugin::registerToMainWindow() { //获取事件服务接口 ctkServiceReference ref; ctkEventAdmin* eventAdmin{nullptr}; ref = context_->getServiceReference<ctkEventAdmin>(); if(ref) { eventAdmin = context_->getService<ctkEventAdmin>(ref); context_->ungetService(ref); } //发送事件 ActionData data; data.action_name = "About"; data.menu_name = "Help"; data.picture = ""; data.num = 1; ActionDictionary a; a.registerAction(data); ctkDictionary message = a.getDictionary(); if(eventAdmin) eventAdmin->postEvent(ctkEvent("mainwindow_action", message)); }
之前关闭的时候崩溃错误
之前的代码关闭的时候mainwindow析构还会报一个线程错误,我们把析构使用信号去处理。
具体修改见项目。这里就不贴优化部分的代码了,思路就是使用信号去处理界面的析构,否则会因为析构代码不在界面线程调用而崩溃。