qml与c++交互最重要的一点就是信号与槽的绑定,本篇博客将介绍在qml定义的信号如何与c++的槽函数绑定。
案例准备,新增自定义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();
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()
{
qDebug() << __FUNCTION__ << __func__;
}
void MyObject::onQmlTestSig(QString name, int age)
{
qDebug() << "name = " << name << " age = " << age;
}
重点关注:MyObject::func()函数和MyObject::onQmlTestSig(QString name, int age)槽函数,其他可忽略。
在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;
// 获得全局对象,上下文对象
QQmlContext *context = engine.rootContext();
// 给qml设置一个全局变量;如果qml内部有定义重名变量,那么会优先使用qml内部定义的变量;另外,定义全局变量会有性能问题
context->setContextProperty("SCREEN_WIDTH", 800);
// 注册,在需要使用的地方 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直接调用C++函数
在MyObject类中,我们定义了一个函数:Q_INVOKABLE void func();
在这个函数头部使用了 Q_INVOKABLE 宏进行修饰,当函数被这个宏就是后,那么就可以被qml直接调用了。
import MyObj 1.0 // 导入自定义模块
Button {
width: 100; height: 50
objectName: "myButton"
onClicked: {
// 调用C++函数
myObj.func()
}
}
MyObject {
id: myObj
}
注意,如果qml端需要直接调用c++端函数,必须使用Q_INVOKABLE 宏进行修饰;c++的槽函数是可以直接被qml调用的,所以不需要加上Q_INVOKABLE 宏进行修饰。
2 在qml端实现qml信号与c++槽函数的绑定
首先在qml定义信号:
// 定义qml信号
signal qmlTestSig(string name, int age)
该信号绑定MyObject类的onQmlTestSig槽函数;
2.1 方式一,通过Connections进行绑定
通过在qml端qmlTestSig信号触发的槽函数中,直接调用c++端槽函数的方式,可以进行信号槽的绑定;
// qml信号绑定c++槽函数方式一
Connections {
target: root
function onQmlTestSig(name, age) {
myObj.onQmlTestSig(name, age)
}
}
2.2 方式二,在初始化完成后进行绑定
在初始化完成后,可以直接使用信号的connect进行绑定;
// qml信号绑定c++槽函数方式二
Component.onCompleted: {
qmlTestSig.connect(myObj.onQmlTestSig) // 在初始化完成后进行信号和槽的绑定
}
2.3 测试
定义一个按钮,在按钮的onClicked函数中发射信号;
Button {
width: 100; height: 50
objectName: "myButton"
onClicked: {
// 调用C++函数
myObj.func()
// 发射信号调用C++槽函数
qmlTestSig("Jtom", 26)
}
}
可以看到,因为上面方式一和方式二进行了两次绑定,所以这里会进行两次的打印;
2.4 main.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: SCREEN_WIDTH
height: 500
title: qsTr("Hello World")
color: "white"
objectName: "window"
// 定义qml信号
signal qmlTestSig(string name, int age)
// qml信号绑定c++槽函数方式一
Connections {
target: root
function onQmlTestSig(name, age) {
myObj.onQmlTestSig(name, age)
}
}
// qml信号绑定c++槽函数方式二
Component.onCompleted: {
qmlTestSig.connect(myObj.onQmlTestSig) // 在初始化完成后进行信号和槽的绑定
}
Button {
width: 100; height: 50
objectName: "myButton"
onClicked: {
// 调用C++函数
myObj.func()
// 发射信号调用C++槽函数
qmlTestSig("Jtom", 26)
}
}
MyObject {
id: myObj
iValue: 20
sString: "this is a custom obj.";
Component.onCompleted: {
console.log("iValue:", 20, " sString:", sString)
}
}
}
3 在c++端实现qml信号与c++槽函数的绑定
在engine加载完成后,可以通过rootObjects函数获得qml端的所有对象;
首先,需要在qml中给定义的控件加上objectName;
如上main.qml中,给Window加上了objectName: "window",给Button加上了objectName: "myButton";
在C++main函数中,在engine加载完成后,获取qml端所有对象;
QList<QObject*> list = engine.rootObjects();
list的首个元素就是main.qml文件中的Window对象,可以通过获取链表的首个QObject打印观察:
// list的首个元素就是window
QObject *windowObj = list.first();
qDebug() << windowObj << " objectName = " << windowObj->objectName();
那么就说明,windowObj变量就是Window控件的对象了;
那么Window控件下的子控件Button如何获得呢?
可以通过获取到父控件后使用findChild模板函数去查找;
现在我们已经获取到Window控件的对象了,就可以直接该对象进行查找子控件对象了;
// 获得button对象
QObject *btnObject = windowObj->findChild<QObject *>("myButton");
qDebug() << btnObject << " objectName" << btnObject->objectName();
那么,既然都可以获取到qml中的对象了,不就可以直接使用QObject::connect函数进行信号与槽的绑定了吗?
没错,是这么回事!
// 信号与槽的绑定
QObject::connect(windowObj, SIGNAL(qmlTestSig(QString, int)),
MyObject::getInstance(), SLOT(onQmlTestSig(QString ,int)));
可以看到,一共打印了三条,说明槽函数被触发了三次,因为信号与槽也绑定了三次嘛;
前两个是在qml中进行绑定的,第三个是在C++中进行绑定的;
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;
// 获得全局对象,上下文对象
QQmlContext *context = engine.rootContext();
// 给qml设置一个全局变量;如果qml内部有定义重名变量,那么会优先使用qml内部定义的变量;另外,定义全局变量会有性能问题
context->setContextProperty("SCREEN_WIDTH", 800);
// 注册,在需要使用的地方 import MyObj 1.0
qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
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();
qDebug() << windowObj << " objectName = " << windowObj->objectName();
// 获得button对象
QObject *btnObject = windowObj->findChild<QObject *>("myButton");
qDebug() << btnObject << " objectName" << btnObject->objectName();
// 信号与槽的绑定
QObject::connect(windowObj, SIGNAL(qmlTestSig(QString, int)),
MyObject::getInstance(), SLOT(onQmlTestSig(QString ,int)));
return app.exec();
}
到此,qml信号与c++槽函数的三种绑定方式已经介绍完毕,依据项目情况使用即可!