Qt Quick QML 与 C++ 交互系列之一

QML 作为一种灵活高效的界面开发语言已经越来越得到业界的认可。QML 负责界面,C++ 负责逻辑,这也是 Qt 官方推荐的开发方式。那么 QML 与 C++ 的交互必然是每一个Qt开发老师需要掌握并且精通的。

接下来,我们会对QML 与 C++ 交互的几种方式进行详细讲解。我们通过创建项目,通过例子来实现、体验并应用这几种交互方式,让我们由浅入深理解其中的原理。

首先,QML 与 C++的交互大致可以分为4种形式:

  1. 注册 C++ 对象到 QML,在 QML 中访问 C++对象;
  2. QML 暴露对象给 C++ 进行交互;
  3. C++ 创建 QML 对象并进行交互;
  4. C++ 对象与 QML 通过信号槽进行交互;

这四种交互方式,是每一个要学习 QML 的程序员必须要深刻理解并熟掌握的。

接下来,我们会分为4章,对 QML 与 C++ 交互的这四种方式 进行详细介绍;今天重点介绍第一种方式:注册 C++ 对象到 QML中,在 QML 中调用 C++ 的方法;

===========================正文开始=============================

在正式开始之前,我们先把今天得重点标出来,并且解释一下:

1. C++ 对象注册到元对象系统

QQmlApplicationEngine::rootContext()->setContextProperty()

2. Q_INVOKABLE 宏定义是将C++ 的 函数(方法)声明为元对象系统可调用的函数 (我们这里先不展开 Q_INVOKABLE ,下面会详细讲到)

 

我们今天介绍的 "注册 C++ 对象到 QML,在 QML 中访问 C++对象" 中,就是应用了上面这两条重要的知识(方法)。接下来我们会使用 Qt (5.12) 自带的IDE Qt Creator 来进行实际操作。

一、 我们首先创建一个新的工程。

(“文件” - “新建文件或项目” - “Application (Qt Quick)” - “Qt Quick Application - Empty”)

 

创建完成就是上面这样子的:里面只有一个main.cpp 与一个默认的 main.qml。 我们既然要做的是 C++ 与 QML 交互,只有一个qml文件是不够的,还需要一个 C++ 类。

二、创建一个C++ 的类 MyQmlClass 来与 QML 进行交互

项目名称 右键 - "Add New..." -  “C++” - “C++ Class” 

类名随便起,我们这里叫做MyQmlClass, 基类选择 QObject 并包含QObject , 然后填写头文件名,源文件名。

这样我们就成功添加了一个C++ 的类。到这里,我们交互双方 C++ 与 QML 的文件都已经有了,我们接下来就可以进入重点了。

首先,我们针对 C++ 类进行改造,增加 一个私有变量 m_Value 与 两个公共的函数对其进行读写:

#ifndef MYQMLCLASS_H
#define MYQMLCLASS_H

#include <QObject>

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

    Q_INVOKABLE void setValue(int value);    
    Q_INVOKABLE int getValue();

signals:

private:
    int m_Value;
};

#endif // MYQMLCLASS_H

其中 定义的两个函数 setValue 与 getValue 分别是对 m_Value 进行写与读,实现如下:

#include "MyQmlClass.h"

MyQmlClass::MyQmlClass(QObject *parent) : QObject(parent)
{

}

void MyQmlClass::setValue(int value)
{
    m_Value = value;
}

int MyQmlClass::getValue()
{
    return m_Value;
}

这时候细心地老师已经发现了在新增的两个函数 与 平时我们定义函数有点不一样,就是函数前增加了一个 Q_INVOKABLE。这是个什么呢?这就是我们正文开始前,给大家说的本节的关键点之一。那 Q_INVOKABLE 什么作用呢? 我们先来看看 QT 官方对 Q_INVOLKABLE 的定义与描述:

翻译一下,大概意思就是说,Q_INVOKABLE 是个宏定义,这个宏将 函数 声明为元对象系统可调用的函数。这里有几个关键点:

  • Q_INVOKABLE 是个宏定义
  • 这个宏定义 针对的是 函数, 不是变量
  • 经过Q_INVOKABLE 声明过得函数 可以被元对象系统调用
  • QtQuick 也在元对象系统内,所以在 QML 中也可以访问这个被声明了的函数

好了,声明了函数,我们现在是不是可以在QML中调用 C++ 的方法了呢? 别着急,还有一个重要的工作要做,就是我们需要将 C++ 的对象,注册到 QML中去。我们需要用到本文开头说的两个重点中的另一个:

QQmlApplicationEngine::rootContext()->setContextProperty()

我们接下来要打开 main.cpp ,通过 QML 引擎 QQmlApplicationEngine 进行注册。 

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "MyQmlClass.h"

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

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    ///
    //声明 对象
    MyQmlClass myQmlImp;

    //将对象进行注册到QML中
    engine.rootContext()->setContextProperty("myQmlImp", &myQmlImp);

    ///

    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();
}

上面的代码中,我们首先定义了一个C++ 的对象 myQmlImp ,然后使用了 QQmlApplicationEngine 获取 其中的 QQmlContext,调用注册的方法 setContextProperty

这里的QQmlContext 类我们这里先不展开,我们就先记住这样用,后面我们再详细的讲一下。对于setContextProperty 函数其实就是设置一个属性,参数就是 key-value

key :自定义字符串,为了好记,我们这里叫做对象的名字 "myQmlImp"

value : 对象引用,对象指针,这里就是&myQmlImp

再来看一下官方的定义:

做完了这两步,我们就做了 C++ 的工作,在 QML 中 就可以 直接拿到对象 myQmlImp 来调用使用 Q_INVOKABLE 定义的方法了。

二、 改造 QML 

 我们的思路是这样的,在QML 中,定义一个“获取”的 Button 与 显示获取值得 Label, 点击 Button 就会获取 C++ 中的 m_value 值, 在 Lable 上进行展示。

1, 我们打开 main.qml 新增加一个 “获取” Button 

我们看到定义Button 的报错了,这是因为新建的项目中,只包含 QtQuick 与 QtQuick.Window 两个包,默认不包含Button 的控件, Button 控件 放在Controls包里面。我们先引入QtQuick.Controls 2.3

导入完 QtQuick.Controls 项目中就包含了三个可用的包,我们以后在具体看一下每个包都是什么功能,包含什么组件。接下来,我们看看刚才的报错好了没?

这时候,我们看到,刚才的错误已经消失了,其实QtQuick.Controls里面,都是Qt官方包装好的一些常用控件,比如收Button、Label, SpinBox, CheckBox, RadioButton等等我们传统QWidget 常见的控件。

我们的思路很简单,就是 QML 中来调用上面 C++ 暴露出来的读写函数。所以我们在QML 中定义一个 “获取” Button ,点击它我们就来调用C++中的 getValue() 函数,然后我们需要一个Label 将获取的 C++ 的值进行展示,所以开始吧:
 

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3

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

    Label{                         //Label用于显示获取C++的值
        id: label                  //显示控件,唯一标识ID:label
        text: ""                   //初始化内容清空

        anchors.bottom: getBtn.top //显示控件的下方放到btn的上方
        anchors.left: getBtn.left  //显示控件的左方与btn的左侧对齐
    }

    Button{                       //Button 用于获取值
        id: getBtn                 //按钮控件,唯一标识ID:getBtn
        text: "获取"                //按钮显示文字
        width: 120                 //按钮宽度
        height: 40                 //按钮高度

        anchors.centerIn: parent   //按钮放到窗口中心

        onClicked: {               //点击按钮事件;
            label.text = myQmlImp.getValue()
        }
    }
}

在代码里,我们定义了一个Label 用来显示获取的值, 定义了一个Button 用来触发获取 C++ 的函数。 其中:

  • 我们将 “获取” 按钮放到窗口中心,将显示Label 放到了 “获取”按钮上方。(对控件定位不太熟悉的老师需要专门学习一下anchors,最基础也是最重要的了)
  • 点击 “获取” 按钮,会执行 onClicked:{} 函数,也就是在 onClicked 里面会调用C++里面暴露的函数
  • myQmlImp 就是 我们在main.cpp 注册的对象的名称。通过myQmlImp 我们就能访问 注册对象里使用Q_INVOKABLE 声明的函数了。

我们看一下‘’效果:

我们点击了 “获取” 按钮,然后在它上方的label 就显示出一串数字。(因为我们在c++ 里面定义的m_Value 没有初始化,所以这个值是个随机的)

到这里,我们就在 QML 中获取了 C++ 代码中的值。可能到这里还有老师感觉不太真实,那么我们就继续进行验证,我们的思路是这样的:

  1. 我们增加一个 TextFiled 用于输入我们想给 。
  2. 再增加一个 “设置” 按钮,将 1中输入的值,给C++ 中的 m_Value 设值。
  3. 然后我们再点击刚才的 “获取” 按钮,将 C++ 中的 m_Value 值读取并显示出来。

这时候我们需要对原来的QML进行改造,新增加一个输入框TextField ,进行数值输入; 还需要新增加一个“设置” 按钮,点击按钮将值赋给 C++ 中的 m_Value

好了,开始:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3

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


    Label{                         //Label用于显示获取C++的值
        id: label                  //显示控件,唯一标识ID:label
        text: ""                   //初始化内容清空

        anchors.bottom: getBtn.top //显示控件的下方放到btn的上方
        anchors.left: getBtn.left  //显示控件的左方与btn的左侧对齐
    }

    Button{                       //Button 用于获取值
        id: getBtn                 //按钮控件,唯一标识ID:getBtn
        text: "获取"                //按钮显示文字
        width: 120                 //按钮宽度
        height: 40                 //按钮高度

        anchors.centerIn: parent   //按钮放到窗口中心

        onClicked: {               //点击按钮事件;
            label.text = myQmlImp.getValue()
        }
    }

    TextField{                      //文字输入控件
        id: textField               //唯一ID
        width: getBtn.width         //也可以直接设置成120
        height: getBtn.height       //也可以直接设置成40

        anchors.top: getBtn.bottom  //放到“获取”按钮下方10个像素
        anchors.topMargin: 10
        anchors.left: getBtn.left   //与“获取”按钮左对齐
    }

    Button{
        id: setBtn
        text: "设置"
        width: textField.width      //可以设置成getBtn.width或者120
        height: textField.height    //可以设置成getBtn.height或者40

        anchors.top: textField.bottom
        anchors.left: textField.left

        onClicked: {
            var value = textField.text
            myQmlImp.setValue(value)
        }

    }


}

代码中,id 为 setBtn 就是我们设置按钮,在他的 onClicked 函数里,我们先获取 TextField 的值,然后通过myQmlImp 调用 使用Q_INVOKABLE 修饰过的setValue 函数。来完成QML对C++ 的另一次操作。

来一起看一下效果:

好了,在这个例子里面,我们在QML中有两次调用了C++ 中的函数,setValue 与 getValue。

最后再来重申一下 QML 与 C++ 交互的第一种方式:注册 C++ 对象到 QML中,在 QML 中调用 C++ 函数。今天的重点有两点:

  1. C++ 中想要暴露的方法必须用 Q_INVOKABLE 修饰
  2. 需要使用对调用对象进行注册
     QQmlApplicationEngine::rootContext()->setContextProperty()
    

    接下来我们会对 QML 与 C++ 交互的剩下三种形式进行详细解读并实践。


这时候,细心地小伙伴可能发现了,我们上面这种方法是:在 C++ 里定义了一个对象,然后将这个对象注册到 QML 里面。在 QML 里面访问的就是 C++ 定义的对象。

那么,如果我不想在C++ 里面定义对象,而是想在 QML 中定义一个对象,并在 QML 中使用, 该怎么办呢?下面一起来学一下将 C++ 类名注册到 QML ,并在QML 声明一个对象并进行访问!

我们首先来认识一下一个新的函数 qmlRegisterType:

 官方描述就是说,qmlRegisterType 就是一个函数模板。将 C++ 的类型注册到 QML 系统中,并且带有版本号,方便版本管理。 我们就把main.cpp 中的函数改造一下:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "MyQmlClass.h"

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

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

//    方式一:注册定义好的对象到 QML
//    MyQmlClass myQmlImp;
//    engine.rootContext()->setContextProperty("myQmlImp", &myQmlImp);

//    方式二:注册类到 QML 对象
    qmlRegisterType<MyQmlClass>("com.company.myqmlclass", 1, 0, "MyQmlClass");

    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();
}

其中:qmlRegisterType 模板函数中的 “com.company.myqmlclass” 为自定义的控件名称,类似于C++中的库名称。我们在 QML 中需要 import 这个控件名, “MyQmlClass” 为 C++ 注册的类名, 1和0 为自定义版本号,方便版本管理。

然后我们就可以在QML 中进行 刚注册的类进行定义一个对象了。

在QML 文件中,我们首先使用 import  com.company.myqmlclass 1.0  类似于 C++ 中的include ,1.0 为 C++ 注册时候带的版本号。

然后我们定义了一个 MyQmlClass 一个对象,id叫做myQmlImp。 这和在C++中定义一个对象,然后将对象注册到 C++ 中效果是一样的。由于这里我们定义的控件ID 与前面例子中的ID是一样的,所以,我们现在点击 “设置”、“获取” 按钮也都是可以使用的,我们来试一下效果:

这样,我们就达到了与方式一一样的效果,不过这种方式多了一个版本管理功能,而且C++ 端注册的时候, C++并没有实例化,而是在QML中进行了实例化。那么如果C++ 类产生很多复杂的派生类,则在QML中可以有一个很好的调试环境。

 

 

 

 

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值