QML与C++交互之C++端信号绑定QML端槽函数

qml与c++交互最重要的一点就是信号与槽的绑定,本篇博客将介绍在c++定义的信号如何与qml的槽函数绑定。

案例准备,新增自定义c++类MyObject,并且注册到qml中;

具体可以查看下方链接:

QML与C++交互之创建自定义对象-优快云博客

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槽函数的绑定方式已经介绍完毕,依据项目情况使用即可!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpp_learners

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值