【qml-3】qml与c++交互第二次尝试(类型方式)

发布于:2025-07-22 ⋅ 阅读:(10) ⋅ 点赞:(0)

背景:

【qml-1】qml与c++交互第一次尝试(实例方式)

【qml-2】尝试一个有模式的qml弹窗-CSDN博客

 【qml-3】qml与c++交互第二次尝试(类型方式)

还是qml学习笔记。

这次搁置太久了。其实不太会,还是以教程为主,但讲课那哥们也是以尝试为主,只是人家经验多。这里吐槽一下,qml目前还是差太多。

官方手册都啥也不是,远不如widget完善好用。版本差异有些挺大,比如学了5再用6,有些真不一样。我干脆从最新的6开始搞,因为5早晚淘汰。

写代码时有些属性明明能用,但就是报错说非法,你得按住ctrl转到类型定义,还不能跳转到头文件,弹窗报错但能复制那个头文件的名,然后去文件系统qt安装目录里找到它再参考,发现里面有这个属性可以用。

先吐槽这些,也许是自己太笨。

回顾:

之前写过实例方式的qml和c++交互。

【qml-1】qml与c++交互第一次尝试(实例方式)

亦即,在c++里直接把类型实例化,把对象指针注册到qml上下文,然后qml直接调用它。

这种方式倒是直截了当,但是从qml里看有些晦涩,冷不丁出来个对象名,拿来就用。

个人感觉这种方式还可以,适合规模小的项目,简单高效。

类型方式:

本次记录的是类型方式,亦即把c++里写好的类,不实例化,而是把类型注册给qml,就像它自己的Button、Item一样,用的时候使用“{}”给它实例化。这么说起来好像更合适一些。那就上demo。

quick项目说明:

这里要插入一段,我觉得按照官方态度,应该是让咱们用Design Studio(DS)做ui,导出到c++的quick项目再混合编程。

这个该死的DS熟悉起来别具一格,等有时间再整理。这里主要说quick项目。

如上图,两种类型,图上选中的是分类类型,构建后qml文件会复制到build目录,发布时跟着exe一起,前后端分离。好处是可以随时改qml不用编译exe,不好就是所谓不安全。

compat那个是把qml放到qrc资源文件,它就一起编译进exe发布,所谓更安全。

隐约记着其实web也有这种概念,以前做c#全栈,我们都是上传aspx,改着方便。

这次demo我选的分离方式,无所谓,改一下很简单。

demo:

如上所说我选的不带compat的quick项目,一路下一步就行,它默认是cmake方式构建。

如上图,我已经添加了一个MyClass类。代码如下:

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>
#include <QQmlEngine>
#include <QDebug>

class MyClass : public QObject
{
    Q_OBJECT
    QML_ELEMENT
public:
    explicit MyClass(QObject *parent = nullptr);

public slots:
    QString onFunc()
    {
        qDebug() << "slot in cpp";
        return "cpp value";
    }
    void onFromQml_GetValue()
    {
        qDebug() << "onFromQml_GetValue";
        emit sigToQml_SendValue("cpp_value");
    }

signals:
    void sigToQml_SendValue(QString);
};

#endif // MYCLASS_H
#include "myclass.h"

MyClass::MyClass(QObject *parent)
    : QObject{parent}
{
    qDebug() << "Class is created in cpp.";
}

已经尽量简单了。主要为了验证一些事。

上面QString onFunc()这个槽函数加了返回值。各位应该记得,qt手册里讲过,connect的队列模式下信号是拿不到返回值的,因为是异步。只有direct和阻塞队列方式可以,也容易理解。这里不深究qt手册了,您可以自己看,要是我记错了咱再讨论。我这里就是为了验证qml和c++之间是个啥情况。

onFromQml_GetValue槽函数就再正常不过了,不多解释。就是让它接受qml发来的信号,再把“返回值”发给qml。我就是想实现qml调用c++,比如查询。

再看qml代码:

import QtQuick
import QtQuick.Controls
import cpp.MyClass

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

    //qml查询信号
    signal sigToCpp_GetValue

    onSigToCpp_GetValue: {
        myclass.onFromQml_GetValue();
    }

    //c++返回信号
    Connections {
        target: myclass
        //以下两种方式都可以,非要共存那就下面的传统方式优先,我试出来的。
        //Connections里面,function方式推荐,官方已经不推荐传统方式了。        
        
        function onSigToQml_SendValue(s) {
            btn.text = s;
            print("-----" + s);
        }

        // onSigToQml_SendValue: (s) => { root.title = s; }
    }

    //C++实例化
    MyClass {
        id: myclass
    }

    Button {
        id: btn
        width:  100
        height: 40
        text: "press me"

        onClicked: {
            // text = myclass.onFunc();//这是可以的
            sigToCpp_GetValue();//发送qml信号
        }
    }
}

CMakeLists.txt

...
qt_add_executable(appuntitled
    main.cpp
    # myclass.h myclass.cpp #注意这里
)

qt_add_qml_module(appuntitled
    URI cpp.MyClass #注意这里
    VERSION 1.0
    QML_FILES
        Main.qml
        SOURCES myclass.h myclass.cpp #注意这里
)
...

对于CMakeList.txt的内容,说明一下。在qml分离方式的项目中添加类,它会在CMakeLists.txt中把头文件和源文件加到qt_add_qml_module函数中,在qml资源方式的项目中,它会加到qt_add_executable函数中。亲测效果没影响,但是个人感觉加在qt_add_executable中更合适。这算一个讨论点。因为目前我没试出来区别。

注意,cmake里设置的URI是qml里import时用的模块,同时对应build目录中那个扩展名是qmltypes的文件路径。至于自己定义的c++类名,和这个没有直接关系。如果有多个c++类要为qml服务,可以都加到某个模块中。就像QtQuick.Controls一样,里面有好多类。

我之前以为,C++里只要写了那个QML_ELEMENT宏就可以自动实现了,但不幸还得改cmake文件,这个URI是个地址,对应build目录里的情况。

之前默认可不是这样的,qt会默认一个untitile,你把它改成自己想要的就行了。当然对应main函数也要改。

因为项目选择的是分离模式,qml是不参与编译,而是单独的文件,所以这里写的路径其实对应build目录里的文件路径,发布时一并复制给目标机即可。

效果:

没问题的,就是点击按钮,由qml向c++发送请求,c++返回数据。

解析:

其实最直接简单的方式就是直接调c++的槽函数就行了,可以带返回值,目前感觉最方便。

之前我以为普通函数也可以,实测不行,必须是槽函数。(后面有补充)

非要发信号走个流程就是上面那样,跟第一篇博客(实例方式)一样道理。就是实例化的时机不一样。

问题:

无论是哪种方式的项目,看教程以及我亲测都会有个问题。每次做完了运行不影响,但qml文件里总有警告,以下三步操作后警告会消失,不一定第几步有效。

1、先运行一下,让它生成build目录,寻址需要。

2、关闭项目,重新打开项目。

3、关闭creator,重新打开。

到这里忍不住还是要吐槽,这可不是我一个人的问题,教程视频里也是如此。还是不完善。您说呢?

补充:

上面说直接调用c++普通函数不行,其实是如果是普通函数,声明时需要在前面加一个宏Q_INVOKABLE。具体看手册吧,这个宏的意义就是让函数可以被元对象系统调用。熟悉qt信号槽的朋友到这里应该明白原因了。qt推崇的松散耦合就是信号槽,元对象系统实现的,qml这种前后端分离的东西显然也是基于元对象的信号槽。这就是为什么直接调用普通函数不可以,而是要加上这个宏。

先记录到这里,本文完。


网站公告

今日签到

点亮在社区的每一天
去签到