【qml】第一次尝试qml与c++交互

背景:

目的是学习qml,因为看到很多qml的酷炫效果,想试一试。

看过网上一些代码,qt提供的工具类好几个,看着就晕。只想提炼一下,做个记录。

我先整理了一套自己的想法:所谓交互,还是qt的信号槽。既然是前后端分离设计,就尽量遵循松散耦合的初衷。后端c++用于写逻辑,就像写库一样,考虑好用途和接口,只要调试通过,就不用管了。只需要把qml当做使用者,去调用c++即可。

为了简单,实例化放在c++中,qml中只管调用即可。

因此,做了一个demo试验一下。

demo:

先用qt新建一个空的quick项目。在c++中添加一个具有信号槽的类,然后在qml中尝试调用它。

先做一个类MyClass.h:

#ifndef MYCLASS_H
#define MYCLASS_H

#include 
#include 

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass();

signals:
    void sigFromCpp(QVariant s);

public slots:
    inline void onCpp(QString s)
    {
        qDebug() << "The cpp slot is called:" << __FUNCTION__ << s;
        qDebug() << "The cpp signal is sent.";
        emit sigFromCpp(s);
    }
};

#endif // MYCLASS_H

Myclass.cpp:

#include "myclass.h"

MyClass::MyClass()
{

}

已经尽量简单。

main.cpp:

#include 
#include 
#include 
#include "myclass.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;


    //--------------------------------------------
    /**
     * 这一段是自己添加的。
     * 实测:放在engine加载前面没有报错。
     * 放在engine加载后面,一样能出结果,但是会有报错。
     * 所以,应该放在前面。
     */
    QQmlContext *oContext = engine.rootContext();
    oContext->setContextProperty("g_iWidth", 500);
    oContext->setContextProperty("g_iHeight", 500);

    MyClass obj;
    oContext->setContextProperty("g_obj", &obj);
    //--------------------------------------------



    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);
    return app.exec();
}

只有中间那一段是自己加的。其中QQmlContext::setContextProperty()函数,就当是在c++中,为qml定义全局变量。我打算用c++的命名习惯来做,所以全局变量一律g_开头,变量名加上类型标识。

所以g_iWidth和g_iHeight表示宽和高,g_obj表示c++对象。

main.qml:

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.12

Window {
    id: mainwindow
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    signal qmlSig(string s)

    function qmlSlot(s) {
        console.log("The qml slot is called:", s)

        //这里使用c++指定的全局变量重新设置窗体大小
        mainwindow.setWidth(g_iWidth)
        mainwindow.setHeight(g_iHeight)
    }

    Button {
        id: btnChangeSize
        text: qsTr("Change size")
        onClicked: {
            //qml如果要“发信号”给c++,两种方式:

            //g_obj.onCpp("The button is clicked.")//直接调用了c++的槽函数

            qmlSig("me")//发信号
        }
    }

    onQmlSig: {
        console.log("The qml signal is sent.")
        g_obj.onCpp("The button is clicked.")
    }

    Connections {
        target: g_obj

        /**
         * 亲测:函数的“形参”可以不写,函数体中使用的变量名,和c++信号中的一样。
         * function onSigFromCpp() {
         *     qmlSlot(s)//是和c++里的信号对应的:void sigFromCpp(QVariant s);
         * }
         *
         * 当然,如果指定了形参,形参的名字可以用,直接用信号里那个也行。
         */
        function onSigFromCpp(ss) {
            qmlSlot(ss)//亲测用s也行
        }

        /**
         * 尝试以下写法,也可以的。
         *
         * onSigFromCpp: {
         *     qmlSlot(s + "234")
         * }
         *
         * 但是,亲测几点注意:
         * 1.不能在后面直接跟参数,像这样不行:onSigFromCpp(s): {...}
         * 2.上面参数直接用“s”,是和c++里的信号对应的:void sigFromCpp(QVariant s);
         * 3.只能写在connections里面,因为qml是按代码块对应的。就像:
         *   Button块里面可以直接onClicked,因为clicked信号是button发出的。
         *   Window块里面可以直接onQmlSig,因为qmlSig信号是在Window块定义的。
         */
    }

}

以上代码中,我认为关键的地方都做了注释。本文最后会有总结。

效果:

运行之后显示带一个按钮的默认窗体,点击按钮之后:

qml响应按钮clicked信号,发出自定义信号qmlSig通知c++;

c++槽函数响应,再发出c++信号给qml;

qml槽函数响应。

之所以要兜一圈,仅仅为了测试信号槽的控制方式。

界面如下:

【qml】第一次尝试qml与c++交互_第1张图片

点击按钮之后,窗体改变大小:

【qml】第一次尝试qml与c++交互_第2张图片

同时调试信息输出如下:

qml: The qml signal is sent.

The cpp slot is called: onCpp "The button is clicked."

The cpp signal is sent.

qml: The qml slot is called: The button is clicked.

达到预期。

要点1:

main.cpp中,如果对engine有设置,需要放在load之前。本次demo就是执行setContextProperty那里,应该放在engine.load之前,如果放在后面,亲测运行效果也能出来,但是调试输出是有错误的:

qrc:/main.qml:34:5: QML Connections: Detected function "onSigFromCpp" in Connections element. This is probably intended to be a signal handler but no signal of the target matches the name.
qrc:/main.qml:35: ReferenceError: g_obj is not defined
qrc:/main.qml:35: ReferenceError: g_obj is not defined
qrc:/main.qml:35: ReferenceError: g_obj is not defined

见名知意不用解释,也许能运行是跟qt内部机制有关,感兴趣可以看源码,但只是使用的话,记住最后load即可。

要点2:

关于MyClass,定义的信号形参类型,现在是QVariant,亲测QString也行。具体以后用到时,一切以运行结果为准。

要点3:

关于main.qml,毕竟它是用来描述ui的,我就姑且认为和QWidget类似,一块一块的区域,就像widget对象树那样。所以如果要定义信号或槽,一定要写在对应的“块”当中。

所谓发qml信号,只需要定义一个信号,然后像函数一样调用即可,没有emit。当然c++里不写emit也行(严谨易读,还是写上)。

qml槽,写在对应块里面时,可以像属性一样直接加冒号,就如:onClicked:{...}既然是“属性”,当然要写在对应的“块”当中。

如果qml槽用于响应含参数的c++信号,两种写法:

要么像函数一样带function关键字,可以带形参,也可以不带形参,不带形参时,函数体中直接使用c++信号中的形参名。

要么像属性一样不带形参,带冒号,因为没有形参,只能使用c++信号中的形参名。

我也不知道为什么,只是今天实测的结果是这样。如果有哪位知道原因或者有更好的建议,请赐教。

本文完。

你可能感兴趣的:(qt/c++,c++,交互,qt,qml)