Поддержка собственных расширений в приложениях
Оглавление
Связь с расширением производится с помощью интерфейса, поэтому приложение должно предоставлять по меньшей мере один интерфейс для использования расширения. Расширения загружаются приложением при помощи класса QPluginLoader, который содержит несколько методов. Самый часто используемый из них — это метод instance(), создающий и возвращающий указатель на объект расширения. Этот класс автоматически производит загрузку расширений, при указании имени файла расширения в его конструкторе. Выгрузку расширения, если в этом есть необходимость, можно осуществить с помощью метода unload(). Программа, показанная на рисунке, демонстрирует приложение, предоставляющее поддержку для использования расширений. Для этого ею предоставляется интерфейс для операций над текстом.
#ifndef _interfaces_h_
#define _interfaces_h_
class QString;
class QStringList;
class StringInterface {
public:
virtual ~StringInterface() {}
virtual QStringList operations() const = 0;
virtual QString operation(const QString& strText,
const QString& strOperation
) = 0;
};
Q_DECLARE_INTERFACE(StringInterface,
"com.mysoft.Application.StringInterface/1.0"
)
#endif //_interfaces_h_
Интерфейс — это класс, который содержит только чисто виртуальные определения методов. В нашем случае, приложение предоставляет только один интерфейс — StringInterafce, из названия которого ясно, что он предназначен для операций над строками. Этот интерфейс объявляет два прототипа методов: operations() — для получения списка операций расширения, и operation() — служащий для вызова операций над строками. Виртуальный деструктор нам нужен для того, чтобы C++ не выдавал предупреждающие сообщения о том, что класс, имеющий виртуальные методы, не имеет виртуального деструктора.
Идентификация интерфейса должна быть задана при помощи макроса Q_DECLARE_INTERFACE(), в котором необходимо указать строку-идентификатор, для которой МОС должен сгенерировать метаинформацию. С ее помощью объект класса QPlugLoader проверяет версию расширения и другую информацию, заданную в этой строке. Строка идентификатора состоит из четырех компонентов, разделенных между собой точками:
- домен создателя интерфейса;
- имя приложения;
- имя интерфейса;
- номер версии.
#ifndef _PluginsWindow_h_
#define _PluginsWindow_h_
#include <QMainWindow>
#include "interfaces.h"
class QLabel;
class QMenu;
class PluginsWindow : public QMainWindow {
Q_OBJECT
private:
QLabel* m_plbl;
QMenu* m_pmnuPlugins;
public:
PluginsWindow(QWidget* pwgt = 0);
void loadPlugins( );
void addToMenu (QObject* pobj);
protected slots:
void slotStringOperation();
};
#endif //_PluginsWindow_h_
Класс основного окна приложения PluginsWindow унаследован от класса QMainWindow. Это сэкономит нам время при работе с меню и лейаутами.
PluginsWindow::PluginsWindow(QWidget* pwgt/*=0*/) : QMainWindow(pwgt)
{
m_plbl = new QLabel("this is the test text");
m_pmnuPlugins = new QMenu("&PluginOperations");
loadPlugins();
setCentralWidget(m_plbl);
menuBar()->addMenu(m_pmnuPlugins);
}
В конструкторе класса создаются виджеты надписи и меню. Виджет надписи (указатель m_plbl) вносится в рабочую область приложения, а меню (указатель m_pmnuPiugins) добавляется вызовом метода addMenu() к основной строке меню. Вызов метода loadPlugins() производит поиск и загрузку расширений.
void PluginsWindow::loadPlugins()
{
QDir dir(QApplication::applicationDirPath());
if (!dir.cd("plugins")) {
QMessageBox::critical(0, "", "plugins directory does not exist");
return;
}
foreach (QString strFileName, dir.entryList(QDir::Files)) {
QPluginLoader loader(dir.absoluteFilePath(strFileName));
addToMenu(qobject_cast<QObject*>(loader.instance()));
}
}
Мы хотим использовать в приложении все возможные расширения. При загрузке расширений мы исходим из того, что они находятся в каталоге \plugins, в котором мы ищем все файлы расширений — для этого мы используем класс QDir. Найденные файлы передаются в конструктор класса QPluginLoader. Затем, возвращенный из объекта QPluginLoader вызовом метода instance() указатель преобразуется к типу указателя на QObject и передается в метод addToMenu(), как возможный кандидат для добавления операций расширения к пунктам меню.
void PluginsWindow::addToMenu(QObject* pobj)
{
if (!pobj) {
return;
}
StringInterface* pI = qobject_cast<StringInterface*>(pobj);
if (pI) {
QStringList lstOperations = pI->operations();
foreach (QString str, lstOperations) {
QAction* pact = new QAction(str, pobj);
connect(pact, SIGNAL(triggered()),
this, SLOT(slotStringOperation())
);
m_pmnuPlugins->addAction(pact);
}
}
}
В методе addToMenu() первым делом проверяется допустимость указателя, то есть его неравенство нулю. В том случае, если указатель окажется равен нулю, будет произведен выход из метода. При помощи qobject_cast<StringInterface*> проверяется доступность поддерживаемого нашим приложением интерфейса в расширении. Представьте себе, что поддерживаемых интерфейсов может быть несколько, а при помощи qobject_cast<T> мы можем отличить один от другого. Если проверка на поддержку интерфейса прошла удачно, то мы, вызовом метода operations(), опрашиваем список всех предоставляемых расширением операций и сохраняем их в переменной lstOperations. Затем, для каждой операции создается объект действия, в который передаются название операции и указатель на объект, являющийся расширением. Созданный объект действия соединяется со слотом slotStringOperation() и добавляется в меню.
void PluginsWindow::slotStringOperation()
{
QAction* pact = qobject_cast<QAction*>(sender());
StringInterface* pI = qobject_cast<StringInterface*>(pact->parent());
m_plbl->setText(pI->operation(m_plbl->text(), pact->text()));
}
Метод sender() возвращает указатель на объект выславший сигнал. Этот указатель приводится к типу указателя на QAction. Вызовом метода parent() из объекта действия мы получаем указатель на объект расширения. Воспользовавшись методом operation(), мы производим действия над текстом виджета надписи. В этот метод мы передаем в текст виджета надписи и название применяемой операции, которое соответствует названию объекта действия.
Читать далее: Создание расширения для приложения