简述:
QML与C++混合编程就是使用QML高效便捷地构建UI,而C++则用来实现业务逻辑和复杂算法。
1> Qt集成了QML引擎和Qt元对象系统,使得QML很容易从C++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如信号、槽函数、枚举类型、属性、成员函数等。
QML访问C++有两个方法:
序号 | 方法 | 备注 |
1 | 在Qt元对象系统中注册C++类,在QML中实例化、访问 | 可以使C++类在QML中作为一个数据类型 |
2 | 在C++中实例化并设置为QML上下文属性,在QML中直接使用 |
2> 在C++中也可以访问QML中的属性、函数和信号。
在C++中加载QML文件可以用QQmlComponent或QQuickView,然后就可以在C++中访问QML对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。
3> 几个重要的类:
序号 | 类 | 作用 | 备注 |
1 | QDeclarativeEngine | 提供了QML的运行环境 | |
2 | QDeclarativeContext | 允许程序使用QML组件显示数据 | |
3 | QDeclarativeComponent | 封装了QML Documents | |
4 | QDeclarativeView | 用于在应用程序开发过程中进行快速原型开发 | 方便的把QML组件嵌入到QGraphicsView中 |
系统:Qt 5 + linux
1、QML访问C++
一个C++类要想被QML访问,必须满足两个条件:
序号 | 条件 |
1 | 从QObject类或QObject类的子类派生继承 |
2 | 使用Q_OBJECT宏 |
这和使用信号与槽的前提条件是一样的。QObject类是所有Qt对象的基类,作为Qt对象模型的核心,提供了信号与槽机制等很多重要特性。这两个条件是为了让一个类能够进入 Qt 强大的元对象系统(meta-object system)中,而使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用。
1.1 C++类的实现
实现一个可被QML访问的类,可以访问类中的信号、槽函数、枚举类型、属性、成员函数等。
1> 几个重要的宏:
序号 | 宏 | 描述 | 备注 |
1 | Q_INVOKABLE | 可使QML中访问C++public或protected成员函数 | 函数返回类型的前面 |
2 | Q_ENUMS | Q_ENUMS 宏将枚举类型注冊到元对象系统中,便可被QML访问 | QML中使用枚举类型的方式是通过C++类型名使用“.”操作符直接访问枚举成员 |
3 | Q_PROPERTY | 用来定义可通过元对象系统訪问的属性 | 能够在 QML 中訪问、改动,也能够在属性变化时发射特定的信号 |
2> Q_PROPERTY宏原型:
Q_PROPERTY( type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL] )
序号 | 项目 | 描述 | 备注 |
1 | name | 属性名,类型可以QVariant支持的任何类型,也可以是自定义类型 | 必选 |
2 | READ | 读取属性值,返回值必须为属性类型或者属性类型的引用或者指针 | 必选 |
3 | WRITE | 设置属性值,返回值必须为void型,带参数,参数类型必须是属性本身的类型或类型的指针或引用 | 可选 |
4 | RESET | 重设属性为默认状态,返回值必须为void型,且不带参数 | 可选 |
5 | NOTIFY | 提供了一个信号,这个信号在值发生改变时会自动被触发 | 可选 |
6 | REVISION | 可选 | |
7 | DESIGNABLE | 表明该属性能在GUI builder(一般为Qt Designer)可见 | 可选 |
8 | SCRIPTABLE | 表明这个属性是否可以被一个脚本引擎操作(默认是true) | 可选 |
9 | STORED | 表明这是一直存在的 | 可选 |
10 | USER | 定义是否可以被用户所编辑 | 可选 |
11 | CONSTANT | 定义属性是不可修改的,所以不能跟WRITE或者NOTIFY同时出现 | 可选 |
12 | FINAL | 表明该属性不会被派生类中重写 | 可选 |
3> 信号和槽
被QML访问的槽必须声明为public或protected。信号在C++中使用时要用到emit关键字,但在QML中就是个普通的函数,用法同函数一样,信号处理器形式为on,Signal首字母大写。信号不支持重载,多个信号的名字相同而参数不同时,能够被识别的只是最后一个信号,与信号的参数无关。
4> C++ 类的实现
#ifndef LOGIN_H
#define LOGIN_H
#include <QObject>
#include <QDebug>
#define GAME "GAME"
class Login: public QObject
{
Q_OBJECT
//枚举类型注册
Q_ENUMS(Color)
//属性声明
Q_PROPERTY(Color color READ getColor WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QString deviceId READ getDeviceId WRITE setDeviceId)
Q_PROPERTY(QString password READ getPassword WRITE setPassword)
public:
Login(QObject *parent) : QObject(parent){ qDebug() << "Welcome.";}
//枚举
enum Color
{
RED,
BLUE,
BLACK
};
//成员函数
Q_INVOKABLE void show(){ qDebug() << "show() is called."; }
Q_INVOKABLE void login(){ qDebug() << "login() is called."; }
Color getColor() const{return m_color;}
void setColor(const Color& color){m_color = color;emit colorChanged();}
QString getDeviceId() {return deviceId;}
void setDeviceId(QString id) {deviceId = id;}
QString getPassword() {return password;}
void setPassword(QString pw) {password = pw;}
public slots:
void doSomething(Color color)
{
qDebug() << "dosomething() is called " << color;
}
signals:
void begin();
void colorChanged();
private:
Color m_color;//属性
QString deviceId;
QString password;
};
#endif // LOGIN_H
1.2 注册C++类
注册一个C++类,并被QML访问,步骤如下表
序号 | 步骤 |
1 | 实现一个C++类,请参考 1.1 |
2 | 将该类注册为QML类型 |
3 | 在QML导入类型 |
4 | 在 QML 创建由 C++ 导出的类型的实例并使用 |
1> QObject派生类可以注册到Qt元对象系统,使得类在QML中同其它内建类型一样,可以作为一个数据类型来使用。QML引擎允许注册可实例化的类型,也可以是不可实例化的类型,常见的注册函数有:
注册函数 | 描述 |
qmlRegisterInterface() | |
qmlRegisterRevision() | |
qmlRegisterSingletonType() | 注冊一个单例类型 |
qmlRegisterType() | 注冊一个非单例的类型 |
qmlRegisterTypeNotAvailable() | 注冊一个类型用来占位 |
qmlRegisterUncreatableType() | 注冊一个具有附加属性的附加类型 |
template<typename T>int qmlRegisterType(const char *uri,int versionMajor, int versionMinor, const char *qmlName);
模板函数注册C++类到Qt元对象系统中,参数说明如下:
参数 | 说明 |
uri | 需要导入到QML中的库/包名 |
versionMajor | 主版本号 |
versionMinor | 次版本号 |
qmlName | 在QML中可以使用的类名 |
例如, 语句 "import QtQuick.Controls 1.1"中, "QtQuick.Controls" 就是包名 uri ,而 1.1 则是版本号,是 versionMajor 和 versionMinor 的组合。
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication app(argc, argv);
//注册C++类型Login
qmlRegisterType<Login>("Login.module",1,0,"Login");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.cpp中将Login类注册为在QML中可以使用的Login类型,主版本为1,次版本为0,库的名字是Login.module。main.qml中导入了C++库,使用Login构造了一个对象,id为login,可以借助id来访问C++。
注册动作必须在QML上下文创建前,否则无效。
2> 在QML中导入C++注册的类型
import Login.module 1.0
3> 在QML文件中导入C++类并使用
import QtQuick 2.9
import QtQuick.Window 2.2
//导入注册的C++类
import Login.module 1.0
Window {
id:root
visible: true
width: 1920
height: 1080
title: qsTr("Login QML")
property string tips: ""
property int count: 0
property var array: [
"qrc:/Res/sound1.wav",
"qrc:/Res/sound2.wav",
"qrc:/Res/sound3.wav"
]
signal finished()
Component.onCompleted: {
console.log("Hello,Hello")
}
MouseArea {
anchors.fill: parent
onClicked: {
//单击鼠标调用begin信号函数
login.begin()
login.show()
//修改属性
login.color = 2
login.password = "123456"
login.deviceId = "admin"
}
}
Login{
id:login //Login类的实例
onBegin: {
doSomething(Login.RED)
}
onColorChanged: {
console.log("color changed.")
}
}
}
在QML中访问C++的成员函数的形式是“.”,如login.show(),支持函数重载。
C++类中的m_color属性可以在QML中访问、修改,访问时调用了color()函数,修改时调用setColor()函数,同时还发送了一个信号来自动更新color属性值。
1.3 QML上下文属性设置
设置QML上下文属性,并被QML访问,步骤如下表
序号 | 步骤 |
1 | 实现一个C++类,请参考 1.1 |
2 | C++设置QML上下文属性 |
3 | 在QML中使用 |
1> C++设置QML上下文属性
Login类先实例化为login对象,然后注册为QML上下文属性
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Login login;
engine.rootContext()->setContextProperty("login", &login);
engine.rootContext()->setContextProperty("GAME", GAME);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include "login.h"
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication app(argc, argv);
QQuickView view;
Login login;
view.rootContext()->setContextProperty("login", &login);
view.rootContext()->setContextProperty("GAME", GAME);
view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
view.show();
return app.exec();
}
engine.rootContext()、viewer.rootContext() 返回的是 QQmlContext 对象。 QQmlContext 类代表一个 QML 上下文,它的 setContextProperty() 方法能够为该上下文设置一个全局可见的属性。
2 > QML 使用
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 1920
height: 1080
title: qsTr("Login QML")
property string tips: ""
property int count: 0
property var array: [
"qrc:/Res/sound1.wav",
"qrc:/Res/sound2.wav",
"qrc:/Res/sound3.wav"
]
signal finished()
Component.onCompleted: {
console.log("Hello,Hello")
}
MouseArea {
anchors.fill: parent
onClicked: {
//单击鼠标调用begin信号函数
login.begin()
login.show()
//修改属性
login.color = 2
login.password = 123456
login.deviceId = admin
}
}
Connections{
target:login
onBegin: {
console.log("begin.")
console.log(GAME)
}
onColorChanged: {
console.log("color changed.")
}
}
}
没有使用 qmlRegisterType()来注册Login类,则qml 中不能再訪问 Login 类,所以不能通过类名来引用它定义的枚举类型,即Login中的枚举类型在QML中是访问不到的。而属性,成员函数,信号和槽均可以访问。
2、C++访问QML
在C++中也可以访问QML中的属性、函数和信号。
2.1 C++访问QML的知识要点
1> 加载QML文件
在C++中加载QML文件可以用QQmlComponent或QQuickView,然后就可以在C++中访问QML对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。
2> 访问QML属性
QObject::property()/setProperty()来读取、修改width属性值。
QQmlProperty::read()/write()来读取、修改height属性值。
如果某个对象的类型是QQuickItem,例如QQuickView::rootObject()的返回值,可以使用QQuickItem::width/setWidth()来访问、修改width属性值。
3>查找组件
QML组件是一个复杂的树型结构,包含兄弟组件和孩子组件,可以使用QObject::findChild()/findChildren()来查找。
方法 | 描述 |
findChild | 返回单个对象 |
findChildren | 返回对象列表 |
//查找 parentWidget 的名为 "button1" 的类型为 QPushButton 的孩子:
QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
//查找 parentWidget 全部名为 "widgetname" 的 QWidget 类型的孩子列表
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
4> 访问QML函数
在C++中,使用QMetaObject::invokeMethod()可以调用QML中的函数,从QML传递过来的函数参数和返回值会被转换为C++中的QVariant类型,成功返回true,参数不正确或被调用函数名错误返回false.。
static bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument());
static inline bool invokeMethod(QObject *obj, const char *member,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
val4, val5, val6, val7, val8, val9);
}
static inline bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType type,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
val3, val4, val5, val6, val7, val8, val9);
}
static inline bool invokeMethod(QObject *obj, const char *member,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
val1, val2, val3, val4, val5, val6, val7, val8, val9);
}
第一个invokeMethod()参数说明:
参数 | 说明 | 备注 |
第一个参数 | 被调用对象的指针 | |
第二个参数 | 访问的函数名 | |
第三个参数 | 连接类型 | |
第四个参数 | 用来接收返回值 | |
其他参数 | 传递被调用方法的参数 | 不能超过10个 |
必须使用Q_ARG()宏来声明函数参数,用Q_RETURN_ARG()宏来声明函数返回值,其原型如下:
QGenericArgument Q_ARG(Type, const Type & value)
QGenericReturnArgument Q_RETURN_ARG(Type, Type & value)
5> C++中使用QML的信号
使用QObject::connect()可以连接QML中的信号,connect()共有四个重载函数,都是静态函数。必须使用SIGNAL()宏来声明信号,SLOT()宏声明槽函数。
使用QObject::disconnect()可以解除信号与槽函数的连接。
2.2 QML中定义信号、函数、元素属性
QML中添加了一个Rectangle,设置objectName属性值为“rect”,objectName是为了在C++中能够找到Rectangle。
QML中添加了qmlSignal()信号和qmlFunction()函数,信号在QML中发送,函数在C++中调用。
import QtQuick 2.9
import QtQuick.Window 2.2
//导入注册的C++类
import Login.module 1.0
Window {
id:root
visible: true
width: 1920
height: 1080
color: "white"
title: qsTr("Login QML")
property string tips: ""
property int count: 0
property var array: [
"qrc:/Res/sound1.wav",
"qrc:/Res/sound2.wav",
"qrc:/Res/sound3.wav"
]
signal finished()
//定义信号
signal qmlSigStart(string message)
Component.onCompleted: {
console.log("Hello,Hello")
}
MouseArea {
anchors.fill: parent
onClicked: {
//单击鼠标调用begin信号函数
login.begin()
login.show()
//修改属性
login.color = 2
login.password = "123456"
login.deviceId = "admin"
//发送信号
qmlSigStart("This is an qml signal.")
}
}
Rectangle{
//设置objectName属性值为“rect”,objectName是为了在C++中能够找到Rectangle
objectName: "rect"
anchors.fill: parent
color:"red"
}
Login{
id:login //Login类的实例
onBegin: {
doSomething(Login.RED)
}
onColorChanged: {
console.log("color changed.")
}
}
//定义函数
function qmlFunction(parameter) {
console.log("qml function parameter is", parameter)
return "function from qml"
}
}
2.3 C++ 中访问QML代码示例
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "login.h"
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication app(argc, argv);
//注册C++类型Login
qmlRegisterType<Login>("Login.module",1,0,"Login");
QQmlApplicationEngine engine;
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
QObject* object = component.create();
qDebug() << "width value is" << object->property("width").toInt();
object->setProperty("width", 1280);//设置window的宽
qDebug() << "width value is" << object->property("width").toInt();
qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
QQmlProperty::write(object, "height", 720);//设置window的高
qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
QObject* rect = object->findChild<QObject*>("rect");//查找名称为“rect”的元素
if(rect)
{
rect->setProperty("color", "blue");//设置元素的color属性值
qDebug() << "color is " << object->property("color").toString();
}
//调用QML中函数
QVariant returnedValue;
QVariant message = "Login from C++";
QMetaObject::invokeMethod(object, "qmlFunction",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, message));
qDebug() << "returnedValue is" << returnedValue.toString(); // function from qml
Login login;
//连接QML元素中的信号到C++ Login类的槽函数slotStart
QObject::connect(object, SIGNAL(qmlSigStart(QString)),
&login, SLOT(slotStart(QString)));
return app.exec();
}
3、QML嵌入到QWidget中方法
QML嵌入到QWidget中方法