前面几章实现了一个很简单的项目结构:控制台生成主窗体,窗体点击弹出一个用户界面。姑且厚着脸皮谈一下框架优化,假设这个项目有N个用户,假设N接近无穷,每次增加用户都要对主窗体进行改动是不友好的;假设我们还要对某些用户进行删除,从代码里,逐一排查插件代码容易发生遗漏。架构中经常使用的方法就是自动加载,本章介绍怎么将原来的项目用自动化方式加载插件。
插件自动加载
本项目所有生成插件都在plugins目录下,因此启用的自动化加载是简单的遍历插件,在main函数中添加函数 :main.cpp
void loadPlugins(QString path, ctkPluginContext* context, QList<QSharedPointer<ctkPlugin> >* list)
{
QDir dir(path);
if(!dir.exists())
return;
QStringList filters;
filters << "*.dylib";
QDirIterator dir_iterator(path, filters, QDir::Files | QDir::Writable,
QDirIterator::Subdirectories);
while (dir_iterator.hasNext()) {
dir_iterator.next();
QFileInfo file = dir_iterator.fileInfo();
QString file_path = file.absoluteFilePath();
if(file_path.contains(".1"))//排除多余连接文件
continue;
qDebug() << file_path;
QUrl url = QUrl::fromLocalFile(file_path);
QSharedPointer<ctkPlugin> plugin;
try
{
plugin = context->installPlugin(url);
}catch(ctkPluginException e){
qDebug() << e.message() << e.getType();
return;
}
list->append(plugin);
}
}
插件间启用逻辑应该改为:主界面启动时,生成用户接口及界面元素。代码层实现应该是:界面插件生成->用户插件生成并向界面注册接口->界面插件生成接口并弹出。于是在MainWindow中注册监听事件:mainwindowplugin.h
class MainWindowPlugin : public QObject, public iMainWindow, public ctkEventHandler { Q_OBJECT Q_INTERFACES(iMainWindow ctkEventHandler) public: MainWindowPlugin(ctkPluginContext *context); virtual void popMainWindow(); protected: virtual void handleEvent(const ctkEvent& event); …… };
mainwindowplugin.cpp
MainWindowPlugin::MainWindowPlugin(ctkPluginContext *context) :m_context(context) { m_windowDlg = new MainWindowDlg(context); ctkDictionary dic; dic.insert(ctkEventConstants::EVENT_TOPIC, "event/registAction"); context->registerService<ctkEventHandler>(this, dic); }
这样它既向外提供了接口调用,又在监听事件,一个插件可注册多种服务。
用户插件在生成时,就向MainWindow发起注册,包括接口信息和约定好的触发事件等参数信息:client1plugin.cpp
void Client1Plugin::registToMainWindow() { ctkServiceReference ref; ctkEventAdmin* eventAdmin; ref = m_context->getServiceReference<ctkEventAdmin>(); if(ref) { eventAdmin = m_context->getService<ctkEventAdmin>(ref); m_context->ungetService(ref); } ctkDictionary message; message.insert("id", "00"); message.insert("name", "用户1"); message.insert("topic","zhimakaimen"); if(eventAdmin) eventAdmin->postEvent(ctkEvent("event/registAction", message)); }
MainWindow中响应函数如下:mainwindowplugin.cpp
void MainWindowPlugin::onRegistAction(const ctkEvent& event) { QString name, id, topic; name = event.getProperty("name").toString(); id = event.getProperty("id").toString(); topic = event.getProperty("topic").toString(); m_windowDlg->registAction(id, name, topic); }
mainwindowdlg.cpp
void MainWindowDlg::registAction(QString id, QString name, QString topic) { QAction* action = new QAction(name); action->setObjectName(id); action->setStatusTip(topic); ui->menubar->addAction(action); connect(action, SIGNAL(triggered(bool)), this, SLOT(action_clicked())); }
当界面点击接口点击时,自动生成事件并发送:mainwindowdlg.cpp
void MainWindowDlg::action_clicked() { if(QAction* action = dynamic_cast<QAction*>(sender())) { //获取事件服务接口 ctkServiceReference ref; ctkEventAdmin* eventAdmin; QString topic = action->statusTip(); ref = m_context->getServiceReference<ctkEventAdmin>(); if(ref) { eventAdmin = m_context->getService<ctkEventAdmin>(ref); m_context->ungetService(ref); } //发送事件 ctkDictionary message; if(eventAdmin) eventAdmin->postEvent(ctkEvent(topic, message)); } }
以上我们不用指定是那个插件需要加载,又是哪个事件需要触发,只要插件在plugins下成功生成,后面的逻辑由代码子自动完成
插件依赖
插件加载时一般根据首字母大小自动加载,所以在Client1启用时,MainWindow还没有被调用,所以发送的"event/registAction"事件没有接收方,这样就要考虑到插件依赖关系,在MANIFEST.MF中添加依赖:
Plugin-SymbolicName:Client1 Plugin-Version:1.0.0 Require-Plugin:MainWindow
这样就向框架申明了,该插件加载时需要先加载MainWindow插件,所有用户插件都应该有这样一份申明
小结
ctk框架搭建系列现在告一段落,所有代码已上传至https://github.com/Iyme/ctkExample。博主内心几乎是崩溃的,因为折腾了一番功夫,似乎也没有看到切实的架构应用场景,所以框架套用应该考虑自身项目是否有这个需求,如果代码量较少强行套框架显得有点得不偿失。
欢迎留言交流,感谢每一个为文章增加点击量的人,也感谢一开始给我很大鼓励的小爬虫。
PS:有朋友说文章排版有问题,我在Safari上没发现问题,最近用Chrome重新登录了下,排版吓哭我。难怪后面几章点击这么少,乱的跟盗版一样能看下去的都是真爱。