qml与c++交互最重要的一点就是信号与槽的绑定,本篇博客将介绍在c++定义的信号如何与qml的槽函数绑定。
案例准备,新增自定义c++类MyObject,并且注册到qml中;
具体可以查看下方链接:
myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
#include <QDebug>
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject(QObject *parent = nullptr); // 构造函数
~MyObject();
static MyObject *getInstance();
const int &iValue() const;
void setIIValue(const int &newIValue);
const QString &sString() const;
void setSString(const QString &newSString);
/**
* @brief func 提供给qml直接调用的函数
*/
Q_INVOKABLE void func();
signals:
void iValueChanged();
void sStringChanged();
/// C++端发送的信号,注意类型必须是QVariant类型,这一点与槽函数不一样!!
void signalQmlTest(QVariant name, QVariant age);
public slots:
// 定义槽函数与qml的信号绑定
void onQmlTestSig(QString name, int age);
private:
int m_iValue;
QString m_sString;
Q_PROPERTY(int iValue READ iValue WRITE setIIValue NOTIFY iValueChanged)
// Q_PROPERTY(QString sString READ sString WRITE setSString NOTIFY sStringChanged)
// 如果值是函数内部成员变量的值,可使用MEMBER去设置,与READ sString WRITE setSString实现效果一致
Q_PROPERTY(QString sString MEMBER m_sString NOTIFY sStringChanged)
};
#endif // MYOBJECT_H
myobject.cpp
#include "myobject.h"
MyObject::MyObject(QObject *parent) : QObject(parent)
{
}
MyObject::~MyObject()
{
}
MyObject *MyObject::getInstance()
{
static MyObject *obj = nullptr;
if (!obj) {
obj = new MyObject;
}
return obj;
}
const int &MyObject::iValue() const
{
return m_iValue;
}
void MyObject::setIIValue(const int &newIValue)
{
if (m_iValue == newIValue) {
return;
}
m_iValue = newIValue;
emit iValueChanged();
}
const QString &MyObject::sString() const
{
return m_sString;
}
void MyObject::setSString(const QString &newSString)
{
if (m_sString == newSString) {
return;
}
m_sString = newSString;
emit sStringChanged();
}
void MyObject::func()
{
// 在C++函数内发送信号
emit signalQmlTest("其他", 24);
qDebug() << __FUNCTION__ << __func__;
}
void MyObject::onQmlTestSig(QString name, int age)
{
qDebug() << "name = " << name << " age = " << age;
}
在代码中,定义了信号:void signalQmlTest(QVariant name, QVariant age);,注意信号类型必须是QVariant类型,否则qml端无法与之绑定成功;这一点与qml端发送信号c++端槽函数绑定不一样,c++端的槽函数可以是具体的类型,具体请看:QML与C++交互之QML端信号绑定C++端槽函数。
该信号在函数void MyObject::func()中被发送出去,这样,当qml端调用c++端func函数时,该信号就会被触发。
在main函数中对MyObject进行注册:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myobject.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// 注册,在需要使用的地方 import MyObj 1.0
qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
1 在qml端进行信号与槽的绑定
导入自定义模块:import MyObj 1.0 // 导入自定义模块
定义qml槽函数:
// 定义qml端槽函数
function qmlSlot(name, age) {
console.log("qml:name = ", name, " age = ", age);
}
定义自定义模块:
MyObject {
id: myObj
}
信号与槽的绑定:
Connections {
target: myObj
function onSignalQmlTest(name, age) {
qmlSlot(name, age)
}
}
注意,onSignalQmlTest槽函数是由c++信号signalQmlTest演变而来,即c++信号发送出去后,会触发onSignalQmlTest槽函数,在槽函数内调用qml的函数即可实现相应功能。
代码整合:
import MyObj 1.0 // 导入自定义模块
// 信号与槽的绑定
Connections {
target: myObj
function onSignalQmlTest(name, age) {
qmlSlot(name, age)
}
}
// 定义qml端槽函数
function qmlSlot(name, age) {
console.log("qml:name = ", name, " age = ", age);
}
// 自定义模块
MyObject {
id: myObj
}
1.1 方式一,直接调用c++端信号
通过按钮直接触发信号:
Button {
width: 100; height: 50
onClicked: {
// 方式一,直接调用c++信号(不太好,信号是在qml端发送出去的)
myObj.signalQmlTest("名字", 25)
}
但是,不太好,因为信号是在qml端发送出去的,不符合我们的要求,我们希望的是信号是在c++端发送。
1.2 方式二,直接调用c++端函数触发信号
Button {
width: 100; height: 50
onClicked: {
// 在c++端的一个函数中发射信号,在qml端直接调用该函数,从而达到信号是从c++端发送出去的
// 其实这样也不太好,在这个案例中,是不需要用到MyObject的,然而为了信号与槽绑定和触发信号而定义了
myObj.func()
}
}
/* 这是c++端函数原型 */
void MyObject::func()
{
// 发送信号
emit signalQmlTest("其他", 24);
qDebug() << __FUNCTION__ << __func__;
}
在c++端的一个函数中发射信号,在qml端直接调用该函数,从而达到信号是从c++端发送出去的;
其实这样也不太好,在这个案例中,是不需要用到MyObject的,然而为了信号与槽绑定和触发信号而定义了这个自定义模块,有点大材小用。
2 注册全局单例
在main函数使用qmlRegisterSingletonInstance函数可以给qml注册全局单例,就可以直接在qml中直接使用对象名去调用函数等操作了,而无需再定义对象。
// 注册,在需要使用的地方 import MyObj 1.0
//qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
// 注册全局单例
qmlRegisterSingletonInstance("MyObj", 1, 0, "MyObject", MyObject::getInstance());
注意,需要注释qmlRegisterType。
此时在qml中国就不需要定义MyObject自定义模块了,可以将其注释调用。
// MyObject {
// id: myObj
// }
在qml中,信号与槽的绑定可以使用MyObject进行处理:
Connections {
//target: myObj
target: MyObject
function onSignalQmlTest(name, age) {
qmlSlot(name, age)
}
}
调用c++函数也可以使用MyObject处理:
MyObject.func()
3 在c++端进行信号与槽的绑定
在main函数中,可以直接使用connect函数进行绑定,但注意,c++端的信号参数类型必须是QVariant类型,否则是绑定不上的。
// 在engine加载完成后,就可以获取qml的所有对象了
QList<QObject*> list = engine.rootObjects();
// list的首个元素就是window
QObject *windowObj = list.first();
// 信号与槽的绑定
QObject::connect(MyObject::getInstance(), SIGNAL(signalQmlTest(QVariant, QVariant)),
windowObj, SLOT(qmlSlot(QVariant, QVariant)));
然后,在qml端,就不能在定义MyObject自定义模块了,否则会报错,需要注释。
// MyObject {
// id: myObj
// }
4 代码整合
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myobject.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// 注册,在需要使用的地方 import MyObj 1.0
// qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
// 注册全局单例
qmlRegisterSingletonInstance("MyObj", 1, 0, "MyObject", MyObject::getInstance());
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
// 在engine加载完成后,就可以获取qml的所有对象了
QList<QObject*> list = engine.rootObjects();
// list的首个元素就是window
QObject *windowObj = list.first();
QObject::connect(MyObject::getInstance(), SIGNAL(signalQmlTest(QVariant, QVariant)),
windowObj, SLOT(qmlSlot(QVariant, QVariant)));
return app.exec();
}
mian.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.14
import MyObj 1.0 // 导入自定义模块
Window {
id: root
visible: true
width: 800
height: 500
title: qsTr("Hello World")
color: "white"
objectName: "window"
// qml端实现信号与槽的绑定
// Connections {
//// target: myObj
// target: MyObject
// function onSignalQmlTest(name, age) {
// qmlSlot(name, age)
// }
// }
// 定义qml端槽函数
function qmlSlot(name, age) {
console.log("qml:name = ", name, " age = ", age);
}
Button {
width: 100; height: 50
objectName: "myButton"
onClicked: {
// 方式一,直接调用c++信号(不太好,信号是在qml端发送出去的)
// myObj.signalQmlTest("名字", 25)
// 方式二,或者在c++端的一个函数中发射信号,在qml端直接调用该函数,从而达到信号是从c++端发送出去的
// 其实这样也不太好,在这个案例中,是不需要用到MyObject的,然而为了信号与槽绑定和触发信号而定义了
// myObj.func()
// 通过对象名去调用函数
MyObject.func()
// MyObject.signalQmlTest("信号", 66)
}
}
// 自定义模块
// MyObject {
// id: myObj
// }
}
到此,c++信号与qml槽函数的绑定方式已经介绍完毕,依据项目情况使用即可!