Qml与C++信号槽交互

前言

在QT开发中,如果遵循MVC架构的话,一般使用QML负责VIEW层显示,使用C++负责数据采集处理。本文仅对QML中使用C++自定义对象,QML的信号与C++自定义对象的槽函数,以及QML中定义的槽函数与C++自定义对象的信号等三部分作一些测试和说明。

1.QML中使用C++自定义对象

1.1.创建自定义C++类

步骤和widgets项目创建c++类一样,如果使用场景可以确定为全局唯一,我们可以直接创建单例。

MyObject.h

class MyObject : public QObject
{
    Q_OBJECT

public:
    explicit MyObject(QObject *parent = nullptr);


    int nValue() const;
    void setNValue(int newNValue);

    int nStaticValue() const;
    void setNStaticValue(int newNStaticValue);

    const QString &sStr() const;
    void setSStr(const QString &newSStr);

private:
    int m_nValue;
    int m_nStaticValue;
    QString m_sStr;

    Q_PROPERTY(int nValue READ nValue WRITE setNValue NOTIFY nValueChanged)

    Q_PROPERTY(int nStaticValue READ nStaticValue WRITE setNStaticValue NOTIFY nStaticValueChanged)

    Q_PROPERTY(QString sStr MEMBER m_sStr NOTIFY sStrChanged)

signals:

    void nValueChanged();
    void nStaticValueChanged();
    void sStrChanged();
};

说明

1.在头文件中定义了三个私有变量,其余都是通过 快捷键alt+enter自动生成的。

如果你在使用该快捷键只能弹出下面三个按钮,说明你的QT版本低于5.15,想要使用上述特性则需要将当前QT版本升级到5.15以上。

2.cpp文件内容是上述快捷键自动生成的,此处略过。

1.2.在合适的地方注册自定义C++供QML使用

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    //注册
    qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
    engine.load(url);

    return app.exec();
}

注意:在engine.load(url)之前注册,尤其是注册单例时,需要确保qml加载时该单例对象已经实例化。

1.3.QML文件中使用自定义C++对象

import QtQuick 2.15
import QtQuick.Window 2.15
import MyObj 1.0

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    property int value: width
    property int staticValue: width

    MyObject{
        id: myobj
        nValue: width
        nStaticValue: 10
        sStr: "hello"
    }
    //当value以 = 号赋值的方式,将变成静止的值
    Component.onCompleted: {
        staticValue = myobj.nStaticValue
        console.log(myobj.sStr)
    }

    onWidthChanged: {
        console.log("staticValue:", staticValue)
        console.log("value:", myobj.nValue)
    }
}

2.QML的信号与C++自定义对象的槽函数

QT的特色之一就是信号槽了,使用信号槽可以使编程变得极为灵活。

2.1.QML中创建信号

//此信号在main.cpp中绑定信号槽
signal sigQml(var i, var s)
//此信号在qml文件中绑定信号槽
signal sigQml2(int i, string s)
//此信号在main.cpp中绑定信号槽
signal sigQmlNoarg()

MyObject中添加槽函数如下:

public Q_SLOTS:
    void slotsCpp(QVariant i, QVariant s);
    void slotsCpp2(int i, QString s);
    void slotsCppClicked();

2.2.QML中直接绑定C++对象中的槽

Component.onCompleted: {
    sigQml2.connect(myobj.slotsCpp2)
}

注意:此处sigQml2的参数类型为int及String可以被slotsCpp2自动识别。

2.3.在C++中绑定QML定义的信号

这里处理稍微麻烦一点,我们需要获取qml中的元素队列,从中遍历出我们需要的对象,然后就是widget项目一样进行绑定即可。

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    //注册
    qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    engine.load(url);

    //在load之后再connect
    auto list = engine.rootObjects();
    QObject* root = list.first();
    //连接信号槽,注意这里的参数类型都是 QVariant
    QObject::connect(root, SIGNAL(sigQml(QVariant,QVariant)), &MyObject::getInstance(), SLOT(slotsCpp(QVariant,QVariant)));
    QObject::connect(root, SIGNAL(sigQmlNoarg()), &MyObject::getInstance(), SLOT(slotsCppClicked()));

    return app.exec();
}

要注意的是这里需要使用QVariant类型,不然会导致信号槽绑定不了。

2.4.小结

通过以上两种信号槽绑定方式的对比,我们能直接感受到2.2QML中直接绑定C++对象中的槽方式更简单,且支持不同的参数类型,而2.3.在C++中绑定QML定义的信号方式则较麻烦,代码量大,尤其是在页面多重嵌套的情况下,获取单个控件可能工作量增大。

3.QML中定义的槽函数与C++自定义对象的信号

3.1.c++自定义对象中创建信号

signals:
    //在qml中调用
    void signalCpp(int i, QString s);
    //在c++中调用
    void signalCpp2(QVariant i, QVariant s);

并且给自定义对象增加一个获取单例的方法:

//头文件
static MyObject * getInstance();

//源文件
static MyObject * __myObj = nullptr;

MyObject * MyObject::getInstance()
{
    if (__myObj == nullptr)
    {
        __myObj = new MyObject();
    }
    return __myObj;
}

3.2.注册单例

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    //qmlRegisterType 需要创建对象才能使用
//    qmlRegisterType<MyObject>("MyObj", 1, 0, "MyObject");
    //qmlRegisterSingletonInstance 使我们可以直接使用单例对象
    qmlRegisterSingletonInstance("MyObj", 1, 0, "MyObject", MyObject::getInstance());
………
………

3.2.1.使用上下文方式储存单例

除了上述注册单例的方式,也可将此单例保存在QML的上下文中,如此同样可以被QML直接使用。

engine.rootContext()->setContextProperty("MyObject", MyObject::getInstance());

3.3.QML中绑定C++对象中的信号

property int clickCount: 0

    //创建槽函数
    function slotQml(i,s)
    {
        console.log("slotQml", i, s)
    }
    //绑定信号槽
    Connections{
        target: MyObject
        function onSignalCpp(i,s)
        {
            slotQml(i,s)
        }
    }

    Button {
        objectName: "btn1"
        width: 100
        height: 30
        onClicked: {
            //发送信号
            MyObject.signalCpp(clickCount, "clickCount" + clickCount)
            clickCount++
        }
    }

Connections函数中的“target: MyObject”即3.2注册单例中的对象名。

3.4.在C++中绑定C++自定义的信号和QML定义的槽

qml中触发:

Button{
        objectName: "btn2"
        y: 60
        width: 100
        height: 30
        onClicked: {
            //发送信号
            MyObject.signalCpp2(clickCount, "clickCount" + clickCount)
            clickCount++
        }
    }
int main(int argc, char *argv[])
{
……
    //qmlRegisterSingletonInstance 使我们可以直接使用单例对象
    qmlRegisterSingletonInstance("MyObj", 1, 0, "MyObject", MyObject::getInstance());
……
    engine.load(url);

    //在load之后再connect
    auto list = engine.rootObjects();
    QObject* root = list.first();
    //绑定信号槽
    QObject::connect(MyObject::getInstance(), SIGNAL(signalCpp2(QVariant,QVariant)), root, SLOT(slotQml(QVariant,QVariant)));

    return app.exec();
}

3.5.小结

通过3.3.QML中绑定C++对象中的信号3.4.在C++中绑定C++自定义的信号和QML定义的槽两种方式对比,直观的感受还是qml中绑定信号槽来得简洁一点,当然在c++绑定也不是不可以。

4.补充(QML和c++函数相互调用)

4.1.QML端调用c++中定义的函数

这个比较简单,只要在自定义对象的函数前使用Q_INVOKABLE 宏即可。

4.2.C++端调用QML中定义的函数

我们一般不会去调用QML中定义的函数,但是QT提供了这个方法。

因为QML中的对象其实也都是继承自QObject,所以一样可以用元对象系统来获取方法属性等。

qml文件中定义函数:

function funcRes(i,s)
{
    return "ok";
}

c++中调用该函数:

    QVariant res;
    QVariant arg1 = 1;
    QVariant arg2 = "jack" ;
    QMetaObject::invokeMethod(root, "funcRes", Q_RETURN_ARG(QVariant, res), Q_ARG(QVariant, arg1), Q_ARG(QVariant, arg2));

    qDebug() << res;

本篇博客到此完结~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值