Qt/QML 插件系統(tǒng)
本文將簡(jiǎn)要介紹一下 Qt 和 QML 的插件系統(tǒng)黍檩,并用幾個(gè)簡(jiǎn)單的示例介紹 QML 的幾種插件的創(chuàng)建方法疲酌。由于時(shí)間所限涮毫,有些地方可能講述的不是很到位翘魄,歡迎溝通指正蜓肆。
1. 插件概述
1.1. 什么是插件
插件(Plug-in颜凯,又稱 addin、add-in仗扬、addon 或 add-on症概,又譯外掛)是一種遵循一定規(guī)范的應(yīng)用程序接口編寫出來(lái)的程序。其只能運(yùn)行在程序規(guī)定的系統(tǒng)平臺(tái)下(可能同時(shí)支持多個(gè)平臺(tái))早芭,而不能脫離指定的系統(tǒng)單獨(dú)運(yùn)行彼城。
1.2. 插件系統(tǒng)組成
主系統(tǒng) —— 通過(guò)插件管理器加載插件,并創(chuàng)建插件對(duì)象退个。一旦插件對(duì)象被創(chuàng)建募壕,主系統(tǒng)就會(huì)獲得插件相應(yīng)的指針或引用,它可以像任何其他對(duì)象一樣使用语盈。
插件管理器 —— 用于管理插件的生命周期舱馅,并將其暴露給主系統(tǒng)使用。它負(fù)責(zé)查找并加載插件刀荒,初始化它們代嗤,并且能夠進(jìn)行卸載棘钞。它還應(yīng)該讓主系統(tǒng)迭代加載的插件或注冊(cè)的插件對(duì)象。
插件 —— 插件本身應(yīng)符合插件管理器的協(xié)議干毅,并提供符合主系統(tǒng)期望的對(duì)象宜猜。
1.3. 為什么要使用插件
為了將模塊從框架中剝離出來(lái),降低框架和功能間的耦合度溶锭,功能的實(shí)現(xiàn)作為模塊單獨(dú)開發(fā)宝恶,而不是功能實(shí)現(xiàn)相關(guān)的復(fù)雜代碼與框架揉合在一起。
解決需求不斷變化的軟件設(shè)計(jì)場(chǎng)景趴捅。
面向未來(lái)垫毙,可以通過(guò)插件來(lái)擴(kuò)展應(yīng)用程序的功能(例如 vscode、qtcreator 的主流 IDE 的插件)拱绑。
2. 插件和動(dòng)態(tài)庫(kù)區(qū)別
2.1. 使用場(chǎng)景
動(dòng)態(tài)庫(kù):解決靜態(tài)庫(kù)編譯時(shí)鏈接符號(hào)表導(dǎo)致的程序占用空間大综芥,庫(kù)升級(jí)時(shí)相關(guān)可執(zhí)行程序需要重新編譯等問(wèn)題。
插件:對(duì)于軟件使用的不同場(chǎng)景猎拨,功能有所區(qū)別時(shí)膀藐,有選擇定制和加載不同的插件,另外插件能降低模塊和主功能間的耦合關(guān)系红省。
2.2. 生命周期
動(dòng)態(tài)庫(kù):程序啟動(dòng)時(shí)加載额各,程序運(yùn)行時(shí)必須保證 .dll/.so 存在,否則無(wú)法正常啟動(dòng)吧恃。
插件:程序運(yùn)行時(shí)到需要的時(shí)候加載虾啦,程序運(yùn)行時(shí)如果 .dll/.so 不存在,也可以正常啟動(dòng)痕寓,只是相應(yīng)插件的功能無(wú)法正常加載和使用而已傲醉。
2.3. 耦合度
動(dòng)態(tài)庫(kù):編譯時(shí)必須指定動(dòng)態(tài)庫(kù)依賴關(guān)系。
插件:編譯時(shí)主程序不知道插件的存在呻率。
3. Qt 中插件的分類
3.1. 純 C++/Qt 插件
3.1.1. 高級(jí) API
高級(jí) API 用于擴(kuò)展 Qt 本身硬毕。
要擴(kuò)展 Qt,需要繼承 Qt 的插件基類礼仗,實(shí)現(xiàn)基類的函數(shù)和添加宏吐咳,最后在將編譯好的插件放置在 Qt 安裝目錄下對(duì)應(yīng)的插件目錄中。需要注意的是藐守,若不將自定義的插件放置在對(duì)應(yīng)的插件子目錄中挪丢,Qt 是不會(huì)加載該插件的。
以下是 Qt 提供的插件基類:
基類 | 插件目錄名稱 | Qt 模塊 | Key 區(qū)分大小寫 |
---|---|---|---|
QAccessibleBridgePlugin | accessiblebridge |
Qt GUI | Case Sensitive |
QImageIOPlugin | imageformats |
Qt GUI | Case Sensitive |
QPictureFormatPlugin (obsolete) | pictureformats |
Qt GUI | Case Sensitive |
QAudioSystemPlugin | audio |
Qt Multimedia | Case Insensitive |
QDeclarativeVideoBackendFactoryInterface | video/declarativevideobackend |
Qt Multimedia | Case Insensitive |
QGstBufferPoolPlugin | video/bufferpool |
Qt Multimedia | Case Insensitive |
QMediaPlaylistIOPlugin | playlistformats |
Qt Multimedia | Case Insensitive |
QMediaResourcePolicyPlugin | resourcepolicy |
Qt Multimedia | Case Insensitive |
QMediaServiceProviderPlugin | mediaservice |
Qt Multimedia | Case Insensitive |
QSGVideoNodeFactoryPlugin | video/videonode |
Qt Multimedia | Case Insensitive |
QBearerEnginePlugin | bearer |
Qt Network | Case Sensitive |
QPlatformInputContextPlugin | platforminputcontexts |
Qt Platform Abstraction | Case Insensitive |
QPlatformIntegrationPlugin | platforms |
Qt Platform Abstraction | Case Insensitive |
QPlatformThemePlugin | platformthemes |
Qt Platform Abstraction | Case Insensitive |
QGeoPositionInfoSourceFactory | position |
Qt Positioning | Case Sensitive |
QPlatformPrinterSupportPlugin | printsupport |
Qt Print Support | Case Insensitive |
QSGContextPlugin | scenegraph |
Qt Quick | Case Sensitive |
QScriptExtensionPlugin | script |
Qt Script | Case Sensitive |
QSensorGesturePluginInterface | sensorgestures |
Qt Sensors | Case Sensitive |
QSensorPluginInterface | sensors |
Qt Sensors | Case Sensitive |
QSqlDriverPlugin | sqldrivers |
Qt SQL | Case Sensitive |
QIconEnginePlugin | iconengines |
Qt SVG | Case Insensitive |
QAccessiblePlugin | accessible |
Qt Widgets | Case Sensitive |
QStylePlugin | styles |
Qt Widgets | Case Insensitive |
定義一個(gè)樣式擴(kuò)展的示例:
繼承 QStypePlugin 基類卢厂,實(shí)現(xiàn)其中的 create 函數(shù)并添加 Q_PLUGIN_METADATA 宏,其他插件實(shí)現(xiàn)時(shí)可能
還需要實(shí)現(xiàn)其他函數(shù)惠啄,具體參考 Qt 文檔慎恒。
mystypeplugin.h 文件:
class MyStylePlugin : public QStylePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "mystyleplugin.json")
public:
QStyle *create(const QString &key);
};
mystypeplugin.cpp:
#include "mystyleplugin.h"
QStyle *MyStylePlugin::create(const QString &key)
{
if (key.toLower() == "mystyle")
return new MyStyle;
return 0;
}
注意任内,由于 QStylePlugin 不區(qū)分大小寫,create 函數(shù)的實(shí)現(xiàn)中使用了小寫來(lái)判斷融柬,但是其他的插件基類區(qū)
分大小寫時(shí)不可以轉(zhuǎn)換成小寫判斷死嗦。
大多數(shù)插件都需要添加一個(gè)提供插件元信息的 JSON 文件,該文件由 Q_PLUGIN_METADATA 宏指定粒氧。JSON 文件中的設(shè)置的信息由插件基類決定越除,需要參考 Qt 的文檔。
mystyleplugin.json:
{ "Keys": [ "mystyleplugin" ] }
有些插件無(wú)須顯示的創(chuàng)建插件對(duì)象外盯,例如數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序摘盆、圖像格式、文本編解碼器等饱苟,Qt 會(huì)在需要的時(shí)
候自己創(chuàng)建孩擂,但是樣式插件例外,需要顯示的創(chuàng)建箱熬。
QApplication::setStyle(QStyleFactory::create("MyStyle"));
Qt 提供的 樣式插件示例 展示了一個(gè)更加完整的如何實(shí)現(xiàn)擴(kuò)展 QStylePlugin 基類插件的方法类垦,大家可以試一下。
由于城须,通常開發(fā)過(guò)程我們很少會(huì)有擴(kuò)展 Qt 的需求蚤认,所以此處不做贅述。
3.1.2. 低級(jí) API
不僅 Qt 本身糕伐,Qt 應(yīng)用程序也可以通過(guò)插件進(jìn)行擴(kuò)展砰琢。在這種情況下,插件可以提供任意的功能赤炒,而不限于數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序氯析、圖像格式、文本編解碼器莺褒、樣式和其他擴(kuò)展 Qt 功能的插件類型掩缓。
低級(jí) API 可以制作應(yīng)用級(jí)別的插件,但是需要在應(yīng)用中使用 QPluginLoader 去檢測(cè)并加載插件遵岩。
使用應(yīng)用插件通常需要以下步驟:
- 定義一個(gè)插件接口你辣,該接口只有虛函數(shù)
- 使用 Q_DECLARE_INTERFACE() 宏告知 Qt 的元對(duì)象系統(tǒng)該接口信息
- 使用 QPluginLoader 加載插件
- 使用 qobject_cast() 測(cè)試插件是否實(shí)現(xiàn)了給定的接口。
編寫應(yīng)用插件需要以下步驟:
- 聲明一個(gè)繼承自 QObject 和該插件要提供的接口的插件類
- 使用 Q_INTERFACES()
宏告知 Qt 的元對(duì)象系統(tǒng)該插件信息 - 使用 Q_PLUGIN_METADATA() 宏導(dǎo)出插件
- 編譯插件項(xiàng)目
例如尘执,這是一個(gè)包含多個(gè)接口類的聲明的頭文件的內(nèi)容:
#ifndef INTERFACES_H
#define INTERFACES_H
#include <QtPlugin>
QT_BEGIN_NAMESPACE
class QImage;
class QPainter;
class QWidget;
class QPainterPath;
class QPoint;
class QRect;
class QString;
class QStringList;
QT_END_NAMESPACE
//! [0]
class BrushInterface
{
public:
virtual ~BrushInterface() {}
virtual QStringList brushes() const = 0;
virtual QRect mousePress(const QString &brush, QPainter &painter,
const QPoint &pos) = 0;
virtual QRect mouseMove(const QString &brush, QPainter &painter,
const QPoint &oldPos, const QPoint &newPos) = 0;
virtual QRect mouseRelease(const QString &brush, QPainter &painter,
const QPoint &pos) = 0;
};
//! [0]
//! [1]
class ShapeInterface
{
public:
virtual ~ShapeInterface() {}
virtual QStringList shapes() const = 0;
virtual QPainterPath generateShape(const QString &shape,
QWidget *parent) = 0;
};
//! [1]
//! [2]
class FilterInterface
{
public:
virtual ~FilterInterface() {}
virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent) = 0;
};
//! [2]
QT_BEGIN_NAMESPACE
//! [3] //! [4]
#define BrushInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface/1.0"
Q_DECLARE_INTERFACE(BrushInterface, BrushInterface_iid)
//! [3]
#define ShapeInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.ShapeInterface/1.0"
Q_DECLARE_INTERFACE(ShapeInterface, ShapeInterface_iid)
//! [5]
#define FilterInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface/1.0"
Q_DECLARE_INTERFACE(FilterInterface, FilterInterface_iid)
//! [4] //! [5]
QT_END_NAMESPACE
#endif
下面是定義插件類的頭文件:
#ifndef EXTRAFILTERSPLUGIN_H
#define EXTRAFILTERSPLUGIN_H
//! [0]
#include <interfaces.h>
#include <QObject>
#include <QtPlugin>
#include <QStringList>
#include <QImage>
class ExtraFiltersPlugin : public QObject, public FilterInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" FILE "extrafilters.json")
Q_INTERFACES(FilterInterface)
public:
QStringList filters() const override;
QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent) override;
};
//! [0]
#endif
有關(guān)該實(shí)例的完整信息舍哄,可以查看 Plug & Paint Example
3.2. 開源的純 QML 插件(qmldir)
3.2.1. 創(chuàng)建不帶 url 前綴的 QML 插件
創(chuàng)建目錄 MyPlugins(本例中我們?cè)谕暾夸?/home/dongshuang/TestQMLPlugin/ 下創(chuàng)建),此目錄是自己定義的誊锭,名稱也可以隨意定義表悬,但是這個(gè)目錄名稱會(huì)作為模塊名稱。
在 MyPlugins 目錄中創(chuàng)建和功能相關(guān)的 qml 文件(MyRect.qml):
import QtQuick 2.12
import QtQuick.Controls 2.12
Item {
anchors.centerIn: parent
Rectangle{
width: 100
height: 100
color: "teal"
Label {
width: 50
height: 20
text: qsTr("TestRect")
}
}
}
在 qml 同級(jí)目錄下創(chuàng)建一個(gè)名為 qmldir
的文件丧靡,并添加如下內(nèi)容:
module MyExamplePlugins
TestRect 1.0 MyRect.qml
3.2.2. 創(chuàng)建帶 url 前綴的 QML 插件
創(chuàng)建目錄 NewPlugins (本例中我們?cè)谕暾夸?/home/dongshuang/TestQMLPlugin/com/mycompany/test/ 下創(chuàng)建)蟆沫,此目錄是自己定義的籽暇,名稱也可以隨意定義,但是這個(gè)目錄名稱會(huì)作為模塊名稱饭庞。
在 NewPlugins 目錄中創(chuàng)建和功能相關(guān)的 qml 文件(NewRect.qml):
import QtQuick 2.12
import QtQuick.Controls 2.12
Item {
Rectangle{
width: 100
height: 100
color: "teal"
Label {
width: 50
height: 20
text: qsTr("NewRect")
}
}
}
在 qml 同級(jí)目錄下創(chuàng)建一個(gè)名為 qmldir
的文件戒悠,并添加如下內(nèi)容:
module NewExamplePlugins
NewRect 1.0 NewRect.qml
3.2.3. 使用 QML 插件
在 pro 文件中添加:
# 環(huán)境變量的設(shè)置只是為了讓 ide 能夠找到插件位置,進(jìn)行高亮,自動(dòng)補(bǔ)全等
QML_IMPORT_PATH += /home/dongshuang/TestQMLPlugin
在 main 函數(shù)中添加如下代碼即可:
// 此處才是真正告訴程序去哪里加載插件
engine.addImportPath("/home/dongshuang/TestQMLPlugin");
在 main.qml 中的使用實(shí)例:
import QtQuick 2.12
import QtQuick.Window 2.12
import MyPlugins 1.0
import com.mycompany.test.NewPlugins 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TestRect {
}
NewRect {
}
}
以上插件實(shí)際上是將源碼目錄直接打包發(fā)布的過(guò)程。
3.3. 隱藏源碼的 QML 插件
在實(shí)際開發(fā)中舟山,我們更多的需要將源碼封裝打包绸狐,而不對(duì)外提供源碼累盗。那又應(yīng)該如何處理呢寒矿?
此時(shí)就需要借助 QQmlExtensionPlugin 這個(gè)類劫窒,以及 Qt 的資源管理系統(tǒng)了搞旭。下面我們用一個(gè)簡(jiǎn)單的實(shí)例來(lái)講解如何實(shí)現(xiàn)翎嫡。
3.3.1. 創(chuàng)建插件工程
首先,我們使用 Qt Creator 的新建一工程向?qū)В瑒?chuàng)建一個(gè) “Library > Qt Quick 2 Extension Plugin”插件工程衙伶。工程名字,我們可以叫 qrcmoduleplugin
,Object class-name 可以隨便填剃袍,因?yàn)楹竺嫖覀円サ羲?URI 的名稱我們定為:com.mycompany.mymodule
3.3.2. 添加 qml 文件
之后我們?cè)?qrcmoduleplugin.pro
文件所在的目錄創(chuàng)建三個(gè) qml 文件:
ButtonBase.qml 文件的內(nèi)容如下:
import QtQuick 2.12
MouseArea {
property alias border: bgObj.border
property alias color: bgObj.color
property alias font: txtObj.font
property alias text: txtObj.text
property alias textAnchors: txtObj.anchors
implicitWidth: 100
implicitHeight: 100
objectName: "ButtonBase"
Rectangle {
id: bgObj
anchors.fill: parent
color: "honeydew"
}
Text {
id: txtObj
text: qsTr("ButtonBase")
}
}
ButtonQrc.qml 文件的內(nèi)容如下:
import QtQuick 2.12
ButtonBase {
color: "lightcoral"
text: qsTr("ButtonQrc")
}
ButtonQrc2.qml 文件的內(nèi)容如下:
import QtQuick 2.12
ButtonBase {
color: "slateblue"
text: qsTr("ButtonQrc2")
}
3.3.3. 創(chuàng)建資源文件
之后我們?cè)?qrcmoduleplugin.pro
文件所在的目錄創(chuàng)建一個(gè)名為 qrcmoduleplugin.qrc
的資源文件黄刚,其內(nèi)容如下:
<RCC>
<qresource prefix="/component">
<file>ButtonQrc.qml</file>
<file>ButtonQrc2.qml</file>
<file>ButtonBase.qml</file>
</qresource>
</RCC>
3.3.4. 修改工程文件的內(nèi)容
之后我們修改 qrcmoduleplugin.pro
文件,主要涉及到其中用數(shù)字標(biāo)記的 5 處:
TEMPLATE = lib
TARGET = qrcmoduleplugin
QT += qml quick
CONFIG += plugin c++11
TARGET = $$qtLibraryTarget($$TARGET)
uri = com.mycompany.mymodule
# 1. 去掉其他的實(shí)現(xiàn)文件
SOURCES += \
qrcmoduleplugin_plugin.cpp
# 2. 去掉其他的頭文件
HEADERS += \
qrcmoduleplugin_plugin.h
DISTFILES = qmldir
!equals(_PRO_FILE_PWD_, $$OUT_PWD) {
copy_qmldir.target = $$OUT_PWD/qmldir
copy_qmldir.depends = $$_PRO_FILE_PWD_/qmldir
copy_qmldir.commands = $(COPY_FILE) "$$replace(copy_qmldir.depends, /, $$QMAKE_DIR_SEP)" "$$replace(copy_qmldir.target, /, $$QMAKE_DIR_SEP)"
QMAKE_EXTRA_TARGETS += copy_qmldir
PRE_TARGETDEPS += $$copy_qmldir.target
}
qmldir.files = qmldir
# 3. 增加安裝資源文件
qrc.files = qrcmoduleplugin.qrc
unix {
installPath = $$[QT_INSTALL_QML]/$$replace(uri, \., /)
qmldir.path = $$installPath
target.path = $$installPath
# 4.指定安裝資源文件位置
qrc.path = $$installPath
INSTALLS += target qmldir qrc
}
# 5. 添加資源文件到工程
RESOURCES += \
qrcmoduleplugin.qrc
3.3.5. 修改插件類的實(shí)現(xiàn)
在這之后民效,我們修改 qrcmoduleplugin_plugin.cpp
文件憔维,這里我們注冊(cè)了兩個(gè) qml 文件,給外部使用畏邢,我們的 ButtonBase.qml
不會(huì)被暴露:
#include "qrcmoduleplugin_plugin.h"
#include <qqml.h>
void QrcmodulepluginPlugin::registerTypes(const char *uri)
{
// @uri com.mycompany.mymodule
qmlRegisterType(QUrl("qrc:/component/ButtonQrc.qml"), uri, 1, 0, "ButtonQrc");
qmlRegisterType(QUrl("qrc:/component/ButtonQrc2.qml"), uri, 2, 0, "ButtonQrc");
}
3.3.6. 拷貝插件資源到指定目錄
之后业扒,我們構(gòu)建工程,完成之后舒萎,就可以在構(gòu)建目錄下生成一系列的文件程储,我們只要拷貝 libqrcmoduleplugin.so
和 qmldir
這兩個(gè)文件到目錄 /home/dongshuang/TestQMLPlugin/com/mycompany/mymodule
中即可,我們可以看到這個(gè)目錄結(jié)構(gòu)其實(shí)是和我們之前定義的 URI (我們的定義為:com.mycompany.mymodule
)有一定的關(guān)聯(lián)的臂寝。而 /home/dongshuang/TestQMLPlugin/
這個(gè)目錄是上節(jié)中我們用到的目錄章鲤,沒錯(cuò),我們之后還會(huì)使用上節(jié)介紹的例子進(jìn)行測(cè)試咆贬。
3.3.7. 生成 .qmltypes 文件
在命令行運(yùn)行如下兩條命令:
$ cd /home/dongshuang/TestQMLPlugin
$ qmlplugindump com.mycompany.mymodule 1.0 /home/dongshuang/TestQMLPlugin > /home/dongshuang/TestQMLPlugin/com/mycompany/mymodule/mymodule.qmltypes
注:如果 qmlplugindump 找不到败徊,需要添加 Qt 的環(huán)境變量。
之后素征,我們就可以在 /home/dongshuang/TestQMLPlugin/com/mycompany/mymodule
目錄中生成 mymodule.qmltypes
文件:
import QtQuick.tooling 1.2
// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump com.mycompany.mymodule 1.0 /home/dongshuang/TestQMLPlugin'
Module {
dependencies: ["QtQuick 2.12"]
Component {
prototype: "QQuickMouseArea"
name: "ButtonQrc 1.0"
exports: ["ButtonQrc 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "border"; type: "QQuickPen"; isReadonly: true; isPointer: true }
Property { name: "color"; type: "QColor" }
Property { name: "font"; type: "QFont" }
Property { name: "text"; type: "string" }
Property { name: "textAnchors"; type: "QQuickAnchors"; isReadonly: true; isPointer: true }
}
Component {
prototype: "QQuickMouseArea"
name: "ButtonQrc 2.0"
exports: ["ButtonQrc 2.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "border"; type: "QQuickPen"; isReadonly: true; isPointer: true }
Property { name: "color"; type: "QColor" }
Property { name: "font"; type: "QFont" }
Property { name: "text"; type: "string" }
Property { name: "textAnchors"; type: "QQuickAnchors"; isReadonly: true; isPointer: true }
}
}
3.3.8. 修改 qmldir 文件
之后我們修改 /home/dongshuang/TestQMLPlugin/com/mycompany/mymodule
目錄中的 qmldir 文件為如下內(nèi)容:
module com.mycompany.mymodule
plugin qrcmoduleplugin
typeinfo mymodule.qmltypes
3.3.9. 使用示例
之后集嵌,我們修改上節(jié)中的示例,將 main.qml 改成如下:
import QtQuick 2.12
import QtQuick.Window 2.12
import MyPlugins 1.0
import com.mycompany.mymodule 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TestRect {
}
ButtonQrc {
anchors.centerIn: parent
}
}
上面的代碼我們可以再試下不改變別的內(nèi)容御毅,而只是把 import com.mycompany.mymodule 1.0
這一句中的 1.0 改成 2.0根欧,再運(yùn)行試試效果。這也是 QML 插件處理不同版本的插件的測(cè)試端蛆。
3.4. 包含 C++ 的 QML 插件
現(xiàn)實(shí)開發(fā)中我們的 QML 插件凤粗,可能需要 C++ 的功能的支持,因此包含 C++ 的 QML 插件也是需要我們學(xué)習(xí)和掌握的。有了上面的兩節(jié)示例的基礎(chǔ)嫌拣,其實(shí) C++ 的部分看起來(lái)就很簡(jiǎn)單了柔袁,這部分大家可以直接參考 QML Plugin Example 這個(gè)示例。
3.4.1. 創(chuàng)建 MinuteTimer 類
MinuteTimer 類主要作用是創(chuàng)建 QBasicTimer 對(duì)象异逐,并啟動(dòng) QBasicTimer 的 start 方法捶索,之后監(jiān)聽 QBasicTimer 在 time out 之后發(fā)出的 timerEvent 事件來(lái)產(chǎn)生時(shí)間變化的信號(hào),同時(shí)計(jì)算和更新當(dāng)前的 hour 和 minute 值灰瞻。
class MinuteTimer : public QObject
{
Q_OBJECT
public:
MinuteTimer(QObject *parent) : QObject(parent)
{
}
void start()
{
if (!timer.isActive()) {
time = QTime::currentTime();
timer.start(60000-time.second()*1000, this);
}
}
void stop()
{
timer.stop();
}
int hour() const { return time.hour(); }
int minute() const { return time.minute(); }
signals:
void timeChanged();
protected:
void timerEvent(QTimerEvent *) override
{
QTime now = QTime::currentTime();
if (now.second() == 59 && now.minute() == time.minute() && now.hour() == time.hour()) {
// just missed time tick over, force it, wait extra 0.5 seconds
time = time.addSecs(60);
timer.start(60500, this);
} else {
time = now;
timer.start(60000-time.second()*1000, this);
}
emit timeChanged();
}
private:
QTime time;
QBasicTimer timer;
};
上述代碼使用了 QBasicTimer 這個(gè)類腥例,該類是 Qt 在內(nèi)部使用的一個(gè)快速、輕量級(jí)的低級(jí)類酝润。如果希望在應(yīng)用程序中使用計(jì)時(shí)器燎竖,我們建議使用更高級(jí)別的 QTimer 類而不是這個(gè)類。注意要销,這個(gè)計(jì)時(shí)器是一個(gè)重復(fù)計(jì)時(shí)器构回,除非調(diào)用 stop() 函數(shù),否則它將發(fā)送后續(xù)計(jì)時(shí)器事件疏咐。
3.4.2. 創(chuàng)建 TimeModel 類
下面是創(chuàng)建用于暴露給 QML 使用的 TimeModel 類纤掸,它主要是對(duì) MinuteTimer 類進(jìn)行單利化的管理和封裝,畢竟時(shí)間應(yīng)該是一樣的凳鬓,對(duì)吧茁肠。其代碼如下:
class TimeModel : public QObject
{
Q_OBJECT
Q_PROPERTY(int hour READ hour NOTIFY timeChanged)
Q_PROPERTY(int minute READ minute NOTIFY timeChanged)
public:
TimeModel(QObject *parent=nullptr) : QObject(parent)
{
if (++instances == 1) {
if (!timer)
timer = new MinuteTimer(QCoreApplication::instance());
connect(timer, &MinuteTimer::timeChanged, this, &TimeModel::timeChanged);
timer->start();
}
}
~TimeModel() override
{
if (--instances == 0) {
timer->stop();
}
}
int minute() const { return timer->minute(); }
int hour() const { return timer->hour(); }
signals:
void timeChanged();
private:
QTime t;
static MinuteTimer *timer;
static int instances;
};
int TimeModel::instances=0;
MinuteTimer *TimeModel::timer=nullptr;
3.4.3. 注冊(cè) TimeModel 類到 QML 插件
在接下來(lái),就是將 TimeModel 類注冊(cè)給 QML 插件:
class QExampleQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char *uri) override
{
Q_ASSERT(uri == QLatin1String("TimeExample"));
qmlRegisterType<TimeModel>(uri, 1, 0, "Time");
}
};
這段代碼中缩举,作者沒有使用 C++ 中的類名垦梆,而是給暴露到 QML 插件中的類另起了一個(gè)簡(jiǎn)單和一目了然的名字 Time。
3.4.4. 改造示例內(nèi)容
接下來(lái)我們像上節(jié)一樣改造一下這個(gè)示例仅孩,使其 qml 文件和圖片資源都能一起發(fā)布到 libqmlqtimeexampleplugin.so
文件中托猩,而不是獨(dú)立的文件。
3.4.4.1. 添加 res.qrc 文件在 pro 目錄
res.qrc 的內(nèi)容如下:
<RCC>
<qresource prefix="/">
<file>imports/TimeExample/center.png</file>
<file>imports/TimeExample/clock.png</file>
<file>imports/TimeExample/Clock.qml</file>
<file>imports/TimeExample/hour.png</file>
<file>imports/TimeExample/minute.png</file>
</qresource>
</RCC>
3.4.4.2. 修改 qmlextensionplugins.pro 文件的內(nèi)容
將其改成如下的樣子:
TEMPLATE = lib
CONFIG += plugin
QT += qml
DESTDIR = imports/TimeExample
TARGET = qmlqtimeexampleplugin
SOURCES += plugin.cpp
qrc.files = qrcmoduleplugin.qrc
qml.files = plugins.qml \
imports/TimeExample/qmldir
qml.path += $$[QT_INSTALL_EXAMPLES]/qml/qmlextensionplugins
target.path += $$[QT_INSTALL_EXAMPLES]/qml/qmlextensionplugins/imports/TimeExample
qrc.path += $$[QT_INSTALL_EXAMPLES]/qml/qmlextensionplugins/res.qrc
INSTALLS += target qml qrc
CONFIG += install_ok # Do not cargo-cult this!
RESOURCES += \
res.qrc
3.4.4.3. 修改 plugin.cpp 文件中的 QExampleQmlPlugin 實(shí)現(xiàn)
將其改成如下形式:
class QExampleQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char *uri) override
{
Q_ASSERT(uri == QLatin1String("TimeExample"));
qmlRegisterType<TimeModel>(uri, 1, 0, "Time");
qmlRegisterType(QUrl("qrc:/imports/TimeExample/Clock.qml"), uri, 1, 0, "Clock");
}
};
3.4.4.4 修改 Clock.qml 文件的內(nèi)容
接下來(lái)我們修改名叫 Clock.qml 的 QML 文件辽慕,它主要用于時(shí)間顯示京腥,其內(nèi)部主要是使用 SpringAnimation 實(shí)現(xiàn)的時(shí)鐘,我們的修改點(diǎn)主要是圖片的資源改為使用 qrc 文件中的資源:
import QtQuick 2.12
Rectangle {
id: clock
width: 200; height: 200; color: "gray"
property alias city: cityLabel.text
property variant hours
property variant minutes
property variant shift : 0
Image { id: background; source: "qrc:/imports/TimeExample/clock.png" }
Image {
x: 92.5; y: 27
source: "qrc:/imports/TimeExample/hour.png"
transform: Rotation {
id: hourRotation
origin.x: 7.5; origin.y: 73;
angle: (clock.hours * 30) + (clock.minutes * 0.5)
Behavior on angle {
SpringAnimation{ spring: 2; damping: 0.2; modulus: 360 }
}
}
}
Image {
x: 93.5; y: 17
source: "qrc:/imports/TimeExample/minute.png"
transform: Rotation {
id: minuteRotation
origin.x: 6.5; origin.y: 83;
angle: clock.minutes * 6
Behavior on angle {
SpringAnimation{ spring: 2; damping: 0.2; modulus: 360 }
}
}
}
Image {
anchors.centerIn: background; source: "qrc:/imports/TimeExample/center.png"
}
Text {
id: cityLabel; font.bold: true; font.pixelSize: 14; y:200; color: "white"
anchors.horizontalCenter: parent.horizontalCenter
}
}
3.4.4.5 修改 qmldir 文件的內(nèi)容
修改工程目錄中的 qmldir
文件的內(nèi)容為:
module TimeExample
plugin qmlqtimeexampleplugin
3.4.5. 構(gòu)建并拷貝資源到指定目錄
接下來(lái)構(gòu)建項(xiàng)目溅蛉,然后找到構(gòu)建目錄公浪,將其中的 libqmlqtimeexampleplugin.so
文件。以及工程目錄中的 qmldir
文件拷貝船侧。然后復(fù)制到 /home/dongshuang/TestQMLPlugin/TimeExample
這個(gè)目錄欠气。
3.4.6. 接著使用 3.2 節(jié)中的項(xiàng)目中 main.qml 進(jìn)行測(cè)試
修改 main.qml 的內(nèi)容,其內(nèi)容其實(shí)是參考示例代碼中的 plugins.qml 文件的內(nèi)容:
import TimeExample 1.0 // import types from the plugin
Clock { // this class is defined in QML (imports/TimeExample/Clock.qml)
Time { // this class is defined in C++ (plugin.cpp)
id: time
}
hours: time.hour
minutes: time.minute
}
我們將 main.qml 的內(nèi)容改為:
import QtQuick 2.12
import QtQuick.Window 2.12
import MyPlugins 1.0
import TimeExample 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TestRect {
}
Clock { // this class is defined in QML (imports/TimeExample/Clock.qml)
anchors.centerIn: parent
Time { // this class is defined in C++ (plugin.cpp)
id: time
}
hours: time.hour
minutes: time.minute
}
}
至此镜撩,我們完成了在 QML 插件中注冊(cè) C++ 類插件的功能预柒,以及將 qml 文件和圖片資源文件一起打包發(fā)布的示例。
4. 參考文章
5. 源代碼
- qrcmoduleplugin.zip 密碼: g13f
- qmlextensionplugins.zip 密碼: qfvl
- TestQMLPlugin.zip 密碼: ck2q