c++和qml之间可以互相调用。利用Qt QML module, c++能够调用qml写的类,因为qml的引擎集成在qt的元对象系统中,所以qml可以调用c++写的类。
我们可以有三种方法,可以在qml中调用c++写的类
- 在qml运行的上下文环境中添加一个类的实例,这样qml中便能使用实例
- 在qml引擎(engine)中注册一个类,之后便能在qml中声明这样的对象
- 在一个插件类中注册需要的类,将其编译成动态库,之后qml从动态库中import所需要的类
所谓c++插件指的是第三种方式。
几个相关的类
QQmlEngine
QQmlEngine类提供一个环境来实例化QML组件。每一个QML组件都是在QQmlContext中实例化的,QQmlContext能将数据传给QML组件,QQmlContext是由QQmlEngine分层级来管理的。
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nText { text: \"Hello world!\" }", QUrl());
QQuickItem *item = qobject_cast<QQuickItem *>(component.create());
//add item to view, etc
QQmlContext
所有QML组件的实例都存在QQmlContext中,因此QQmlContext需要提供qml值的解释环境。正如我们在一个普通qml文件中看见的那样,我们可以在一个qml对象里面,定义很多其他的qml对象,因此一个qml文件可以看成一颗qml对象树。
import QtQuick 2.0
Item {
Rectangle {
id: rect1
x: 12; y: 12
width: 76; height: 96
color: "lightsteelblue"
}
Rectangle {
id: rect2
x: 112; y: 12
width: 76; height: 96
border.color: "lightsteelblue"
border.width: 4
radius: 8
}
}
每一个qml对象都有一个上下文来提供其值的解释。我们可以使QQmlEngine::rootContext() 来接触最顶层的上下文。
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QQmlEngine engine;
Message msg;
engine.rootContext()->setContextProperty("msg", &msg);
QQmlComponent component(&engine, QUrl::fromLocalFile("MyItem.qml"));
component.create();
return app.exec();
}
假如我们已经在其他地方对类Message进行了定义,那么上面这段代码这就是直接在qml运行的上下文中添加了一个类的实例,从而可以在qml中直接用msg这个类,这便是qml调用c++类的第一种类型。
engine.rootContext()->setContextProperty("msg", &msg);
分析这条语句:&msg是需要添加到上下文的类的指针,“msg”是在qml中这个类的新名字,也就是在qml中,用“msg”来指代&msg指针所指向的类。
##如何定义qt类
我们知道qt引以为豪的信号槽机制是通过继承QObject类来实现的,qt的元对象系统将c++传统的类进行了扩展,添加了属性、信号和槽。正如我们在编写qml时所了解的那样,一个qml对象有属性、信号、槽和方法。这样qml对象和qt中继承QObject的对象可以建立一种对应的关系。用了qt特性的类,都需要在定义类的开始加入Q_OBJECT宏,这个宏将赋予这个类很多qt的特性,例如属性,信号槽,类的反思,即对类名的敏感。我们可以写一个qt中典型的类:
people.h文件
#ifndef PEOPLE_H
#define PEOPLE_H
#include <QObject>
class People : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString idnum READ idnum WRITE setIdnum NOTIFY idnumChanged)
public:
explicit People(QObject *parent = 0);
QString name() const;
QString idnum() const;
void setName(const QString &s);
void setIdnum(const QString &s);
Q_INVOKABLE void sayHello();
signals:
void nameChanged();
void idnumChanged();
public slots:
void sayMyName();
private:
QString m_name;
QString m_idnum;
};
#endif // PEOPLE_H
这段代码值得注意的是:
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
这段代码定义这个类有一个叫name的属性,我们可以读属性的值,也可以改写属性的值,由于qml中可以将属性的值进行绑定,所以当一个属性的值变化时,需要发出改变此值的信号,方便和该值绑定的其他属性捕捉该信号,更新其他属性的值。属性值的绑定值指的是,将一个属性A和属性B绑定,则属性B的值就是属性A的值,当属性B的值发生改变,属性A的值也随之发生改变。例如
在main.qml文件中
import QtQuick 2.3
import QtQuick.Window 2.2
import Module 1.0
Window {
visible: true
Rectangle {
id: rect1
width: 100; height: 100
color: "yellow"
}
Rectangle {
id: rect2
width: 100; height: 100
color: rect1.color
anchors.top: rect1.bottom
}
People {
id: p0
name: "David"
idnum: "123456"
}
Text {
text: p0.idnum
}
}
这里就将rect2.color和rect1.color绑定起来了,改变了rect1.color的值,就改变了rect2.color的值。
定义属性还需要定义属性怎么读,怎么写,怎么发出改变的信号。
public:
explicit People(QObject *parent = 0);
QString name() const;
QString id() const;
void setName(const QString &s);
void setId(const QString &s);
Q_INVOKABLE void sayHello();
signals:
void nameChanged();
void idChanged();
具体的定义在people.cpp文件中
#include “people.h”
#include
People::People(QObject *parent) : QObject(parent)
{
}
QString People::name() const
{
return m_name;
}
QString People::id() const
{
return m_id;
}
void People::setName(const QString &s)
{
if (s != m_name) {
m_name = s;
emit nameChanged();
}
}
void People::setId(const QString &s)
{
if (s != m_id) {
m_id = s;
emit idChanged();
}
}
void People::sayHello()
{
qDebug() << "Hello, world!";
}
void People::sayMyName()
{
qDebug() << m_name;
}
我们还可以在类里面定义信号槽、函数(前面需要添加Q_INVOKABLE宏)
public:
explicit People(QObject *parent = 0);
QString name() const;
QString id() const;
void setName(const QString &s);
void setId(const QString &s);
Q_INVOKABLE void sayHello();
signals:
void nameChanged();
void idChanged();
具体定义在上面提到的people.cpp里。
在qml引擎中注册类
文件结构是这样的
main.cpp文件
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQuick/QQuickView>
#include "people.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<People>("Module", 1, 0, "People");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
为了能够让qml中能够使用c++定义的类,需要在main.cpp中添加一些代码:
qmlRegisterType<People>("Module", 1, 0, "People");
这段代码的意思是将People类型注册到qmlengine中,将这个类命名为”People”,放在名为”Module”的库中,版本为1.0。声明不同版本号的意义在开发的过程中我们会对类进行进行,会有不同版本的类,我们将不同版本的类放在不同版本的库中,方便使用。
编写插件
所谓插件就是一个定义好接口的动态库。我们首先要生成插件,当要使用插件的时候需要先安装好插件。
生成插件
如果我们使用QtCreator那我们可以使用向导来建立一个插件。在Libary下点击Qt Quick 2 Extension Plugin,输入插件名和要注册的类名之后得到如下的文件结构:
其中add_plugin.h和add_plugin.cpp中定义了名为Adder的插件(输入的插件名),myadder.h和myadder.cpp定义了我们要注册的类Myadder(输入的要注册的类名)。
Adder这个类继承QQmlExtensionPlugin。其一般形式如下:
在adder_plugin.h文件中
#ifndef ADDER_PLUGIN_H
#define ADDER_PLUGIN_H
#include <QQmlExtensionPlugin>
class AdderPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri);
};
#endif // ADDER_PLUGIN_H
在adder_plugin.cpp文件中
#include "adder_plugin.h"
#include "myadder.h"
#include <qqml.h>
void AdderPlugin::registerTypes(const char *uri)
{
// @uri operators
qmlRegisterType<MyAdder>(uri, 1, 0, "MyAdder");
}
其中
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") 声明这个类为qml的插件。
void AdderPlugin::registerTypes(const char *uri)
{
// @uri operators
qmlRegisterType<MyAdder>(uri, 1, 0, "MyAdder");
} 将MyAdder这个类注册到qml引擎中,将其放在uri的1.0版本的库中,qml通过"MyAdder"这个名字来用这个类。uri就是库名,uri是在qmldir文件中声明的。
在qmldir文件中
module operators
plugin Adder
第一行是将uri指定为“operators”,第二行指定这个qmldir所代表的插件为Adder。qmldir文件的作用是将插件的库(uri地址)暴露出来,方便调用的时候寻址。
方便起见,我们可以在pro文件里添加这样的语句
DESTDIR = ../operators
# Copy the qmldir file to the same folder as the plugin binary
QMAKE_POST_LINK += $$QMAKE_COPY $$replace($$list($$quote($$PWD/qmldir) $$DESTDIR), /, $$QMAKE_DIR_SEP)
第一行的意思是: 这个插件编译出来的库的地址是上一级目录的operators。
第二行的意思是: 将qmldir文件拷贝到上面的地址,这样让编译出来的库和qmldir文件在同一个目录下。在同一个目录下的意义是,当调用插件时,解析了qmldir文件,去uri指定的地址,也就是当前目录下寻找库文件。
编译该工程,我们得到
在mac os 系统下编译,该目录有
这样我们就得到插件了,理论上我们可以将得到的文件夹放到任意的位置,只要使用插件的时候导入正确的地址就可以了。
###使用插件 我们在QtCreator中建立一个普通的qt quick application来使用该插件。
我们可以在main.qml中通过
import operators 1.0
之后,来使用MyAdder这个类,使用的方法和一般的qml对象一样:
MyAdder {
id: adder
}
为了能够在qml文件中顺利import operators 1.0,我们要在main.cpp中做点修改。
main.cpp文件
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImportPath("../");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
添加了这个语句
engine.addImportPath("../");
意思是,添加一个路径,在这个路径下搜索按qmldir中uri的格式来寻找插件。因为我们默认使用影子编译,即编译出来的文件单独在一个文件夹,和源文件分离。编译的地址默认为影子编译编译出来的文件夹,而我们一般讲插件这么放
所以我们一般这样
engine.addImportPath("../");
添加语句。
这样我们就能在这个工程下顺利使用我们的插件了。