用户名 立即注册 找回密码

微雪课堂

搜索
微雪课堂 树莓派 树莓派QT教程 查看内容

树莓派Qt系列教程29(上):Qml和C++混合编程

2020-9-3 14:44| 发布者: dasi| 查看: 29373| 评论: 0|原作者: dasi

摘要: 前言最后这几节我们要给大家讲一下如何使用Qml和C++混合编程,大家都知道,Qt quick能够使我们的界面生成非常绚丽的效果,但是,它本身也是有局限性的,对于一些业务逻辑和复杂算法,比如低阶的网络编程如 QTcpSocke ...

前言

最后这几节我们要给大家讲一下如何使用Qml和C++混合编程,大家都知道,Qt quick能够使我们的界面生成非常绚丽的效果,但是,它本身也是有局限性的,对于一些业务逻辑和复杂算法,比如低阶的网络编程如 QTcpSocket ,多线程,又如 XML 文档处理类库 QXmlStreamReader / QXmlStreamWriter 等等,在 QML 中要么不可用,要么用起来不方便。所以呢,我们基于这种原因来混合编程。

原理和方法

简单来说,混合编程就是通过Qml高效便捷的构建UI界面,而使用C ++来实现业务逻辑和复杂算法。Qt集成了QML引擎和Qt元对象系统,使得QML很容易从C ++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如信号、槽函数、枚举类型、属性、成员函数等。

要想在Qml中访问C ++对象,必然要找到一种方法在两者之间建立联系,而Qt中提供了两种在 QML 环境中使用C ++对象的方式:

  • 在C ++中实现一个类,注册到Qml环境中,Qml环境中使用该类型创建对象
  • 在C ++中构造一个对象,将这个对象设置为Qml的上下文属性,在Qml环境中直接使用该属性

两种方式之间的区别是第一种可以使C ++类在QML中作为一个数据类型,例如函数参数类型或属性类型,也可以使用其枚举类型、单例等,功能更强大。

实现可以被QML访问的 C++ 类

先来看第一种方式,C++类要想被QML访问,首先必须满足两个条件:一是派生自QObject类或QObject类的子类,二是使用Q_OBJECT宏。

QObject类是所有Qt对象的基类,作为Qt对象模型的核心,提供了信号与槽机制等很多重要特性。Q_OBJECT宏必须在private区(C++默认为private)声明,用来声明信号与槽,使用Qt元对象系统提供的内容,位置一般在语句块首行。

我们还是新建一个Qt quick工程,然后我们一点一点的来看如何实现可以被QML访问的C++类。首先肯定是需要构建一个类。

信号和槽

只要是信号和槽,都可以在 QML 中访问,可以把 C++ 对象的信号连接到 QML 中定义的方法上,也可以把 QML 对象的信号连接到 C++ 对象的槽上,还可以直接调用 C++ 对象的槽或信号……所以,这是最简单好用的一种途径。

在mixing.h文件中定义一个信号和一个槽函数

01#ifndef MIXING_H
02#define MIXING_H
03 
04#include <QObject>
05#include <QColor>
06 
07class Mixing : public QObject
08{
09    Q_OBJECT
10public:
11    explicit Mixing(QObject *parent = nullptr);
12 
13signals:
14    void colorChanged(const QColor & color);
15 
16public slots:
17    void start();
18};
19 
20#endif // MIXING_H

Mixing类中的信号colorChanged()和槽函数start都可以被Qml访问,但是注意槽必须被声明为public或protected,而且信号在 C++ 中使用时要用到emit关键字,但是在Qml中就是个普通的函数。我们想要实现的效果是点击鼠标,改变窗体颜色,看一下信号和槽是如何在Qml和 C++中传递的。

在mixing.cpp中声明槽函数

01#include "mixing.h"
02#include <QDebug>
03 
04Mixing::Mixing(QObject *parent) : QObject(parent)
05{
06 
07}
08 
09void Mixing::start()
10{
11    qDebug() << "start";
12    emit colorChanged(Qt::blue);
13}

这里传递了一个颜色到Qml中,当然,现在我们肯定还无法在qml文件中使用Mixing类,那么如何将Mixing类注册为Qml类型呢?其实有很多办法,我们这里就举一个最常规的注册类型qmlRegisterType。它比较常见的原型:

1template<typename T>
2int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

模板参数typename ,就是你要实现的 C++ 类的类名。它的第一个参数uri ,让你指定一个唯一的包名,类似Java 中的那种,一是用来避免名字冲突,二是可以把多个相关类聚合到一个包中方便引用。比如我们常写这个语句 "import QtQuick.Controls 2.3" ,其中的 "QtQuick.Controls" 就是包名 uri ,而2.3则是版本,是versionMajorversionMinor的组合。 qmlName则是 QML中可以使用的类名。

我们通过qmlRegisterType将Mixing注册为qml类型,修改main.cpp文件

01#include <QGuiApplication>
02#include <QQmlApplicationEngine>
03#include <QtQml>
04#include "mixing.h"
05 
06int main(int argc, char *argv[])
07{
08    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
09 
10    QGuiApplication app(argc, argv);
11 
12    qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");
13 
14    QQmlApplicationEngine engine;
15    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
16    if (engine.rootObjects().isEmpty())
17        return -1;
18 
19    return app.exec();
20}

我们把Mixing类注册成为Qml类型Mixing,主版本是1,次版本是0,包名是an.qt.Mixing。注意:注册动作一定要放在 QML 上下文创建之前,否则的话,注册是没有用的。

接下来我们就可以在qml文件中导入Mixing类,并且使用它了。

01import QtQuick 2.9
02import QtQuick.Window 2.2
03import an.qt.Mixing 1.0
04 
05Window {
06    id:root
07    visible: true
08    width: 640
09    height: 480
10    title: qsTr("mixing")
11     
12    MouseArea{
13        anchors.fill: parent
14        onClicked: {
15            mixing.start()
16        }
17    }
18 
19    Mixing{
20        id: mixing
21        onColorChanged: {
22            root.color = color
23        }
24    }
25}

Mixing这时就像Qml中一个普通的类型一样使用了,点击鼠标,调用Mixing中的start函数,输出“start”,改变窗体的颜色为蓝色。

枚举类型

如果想要在注册的类中使用枚举类型,可以使用Q_ENUMS 宏将该枚举注册到元对象系统中。修改mixing.h文件

01class Mixing : public QObject
02{
03    Q_OBJECT
04    Q_ENUMS(BALL_COLOR)
05 
06public:
07    explicit Mixing(QObject *parent = nullptr);
08 
09    enum BALL_COLOR{
10        BALL_COLOR_YELLOW,
11        BALL_COLOR_BLUE,
12        BALL_COLOR_GREEN,
13    };
14 
15signals:
16    void colorChanged(const QColor & color);
17 
18public slots:
19    void start(BALL_COLOR ballColor);
20};

我们注册了枚举类型之后,在Qml中就可以用 ${CLASS_NAME}.${ENUM_VALUE} 的形式来访问了。看一下qml文件中的内容。主要修改的是MouseArea部分。

01MouseArea{
02    anchors.fill: parent
03    acceptedButtons:Qt.LeftButton | Qt.RightButton;
04 
05    onClicked: {
06        if(mouse.button === Qt.LeftButton)
07        {
08            mixing.start(Mixing.BALL_COLOR_BLUE)
09        }else if(mouse.button === Qt.RightButton){
10            mixing.start(Mixing.BALL_COLOR_GREEN)
11        }
12    }
13 
14    onDoubleClicked: {
15        mixing.start(Mixing.BALL_COLOR_YELLOW)
16    }
17}

从上面的代码可以看出,调用枚举类型的方法形如Mixing.BALL_COLOR_BLUE,注意前面是类名哦,不是设置的id。我们这次要实现的效果是点击鼠标左键,窗口颜色变蓝;点击鼠标右键,窗口颜色变绿;双击鼠标,窗口颜色变黄。

然后将逻辑处理部分写到mixing.cpp文件。

01void Mixing::start(BALL_COLOR ballColor)
02{
03    QColor color;
04    qDebug() << "start";
05    switch (ballColor) {
06        case BALL_COLOR_BLUE:
07            color = Qt::blue;
08            break;
09        case BALL_COLOR_GREEN:
10            color = Qt::green;
11            break;
12        case BALL_COLOR_YELLOW:
13            color = Qt::yellow;
14            break;
15    }
16    emit colorChanged(color);
17}

用了一个switch选择器来实现颜色的切换。效果如下:

C++ 类的属性和成员函数

在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,但是注意的是在QML中访问的前提是public或protected成员函数,而且这个宏必须放在返回函数前面。

而定义属性则需要使用Q_PROPERTY 宏,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。要想使用 Q_PROPERTY 宏,定义的类必须是QObject的后裔,必须在类首使用Q_OBJECT宏。

Q_PROPERTY宏的原型:

01Q_PROPERTY(type name
02           (READ getFunction [WRITE setFunction] |
03            MEMBER memberName [(READ getFunction | WRITE setFunction)])
04           [RESET resetFunction]
05           [NOTIFY notifySignal]
06           [REVISION int]
07           [DESIGNABLE bool]
08           [SCRIPTABLE bool]
09           [STORED bool]
10           [USER bool]
11           [CONSTANT]
12           [FINAL])

属性的type、name是必需的,其它是可选项,最常用的有READ、WRITE、NOTIFY。属性的type可以是QVariant支持的任何类型,也可以是自定义类型,包括自定义类、列表类型、组属性等。另外,属性的READ、WRITE、RESET是可以被继承的,也可以是虚函数,这些特性并不常用。

  • READ:读取属性值,如果没有设置MEMBER的话,它是必需的。一般情况下,函数是个const函数,返回值类型必须是属性本身的类型或这个类型的const引用,没有参数。
  • WRITE:设置属性值,可选项。函数必须返回void,有且仅有一个参数,参数类型必须是属性本身的类型或这个类型的指针或引用。
  • NOTIFY:与属性关联的可选信号。这个信号必须在类中声明过,当属性值改变时,就可触发这个信号,可以没有参数,有参数的话只能是一个类型同属性本身类型的参数,用来记录属性改变后的值。

我们从代码中看一下如何定义,修改mixing.h文件

01class Mixing : public QObject
02{
03    Q_OBJECT
04    Q_ENUMS(BALL_COLOR)
05    Q_PROPERTY(unsigned int number READ Number WRITE setNumber NOTIFY Numberchanged)
06 
07 
08public:
09    explicit Mixing(QObject *parent = nullptr);
10 
11    enum BALL_COLOR{
12        BALL_COLOR_YELLOW,
13        BALL_COLOR_BLUE,
14        BALL_COLOR_GREEN,
15    };
16 
17    unsigned int Number() const;
18    void setNumber(const unsigned int &Number);
19 
20    Q_INVOKABLE void stop();
21 
22signals:
23    void colorChanged(const QColor & color);
24    void Numberchanged();
25 
26public slots:
27    void start(BALL_COLOR ballColor);
28 
29private:
30    unsigned int m_Number;

可以看到我们通过Q_INVOKABLE修饰了stop函数。通过Q_PROPERTY修饰了名为number的属性,number通过Number函数读得数据,通过setNumber函数写入数据,触发信号是Numberchanged函数。在cpp文件中写这几个函数的内容。

01unsigned int Mixing::Number() const
02{
03    return m_Number;
04}
05 
06void Mixing::setNumber(const unsigned int &number)
07{
08    if(number != m_Number)
09    {
10        m_Number = number;
11        emit Numberchanged();
12    }
13}
14 
15void Mixing::stop()
16{
17    qDebug() << "颜色改变啦!!!";
18}

接下来我们就可以在Qml中调用函数和属性了,打开界面什么都不做时,会输出number的初始值,因为没有为其初始化,所以大家返回的数据可能不一定为0。双击时,会设置number的值,这里我们设置的是10,主要就是通过setNumber来写数的。当number的值改变,会触发Numberchanged信号,发射出去。所以还需要在Qml中写一个信号处理函数,也是输出number的值,不过现在输出的就是改变之后的number了。

01MouseArea{
02    anchors.fill: parent
03    acceptedButtons:Qt.LeftButton | Qt.RightButton;
04 
05    onClicked: {
06        if(mouse.button === Qt.LeftButton)
07        {
08            mixing.start(Mixing.BALL_COLOR_BLUE)
09        }else if(mouse.button === Qt.RightButton){
10            mixing.start(Mixing.BALL_COLOR_GREEN)
11        }
12    }
13 
14    onDoubleClicked: {
15        mixing.start(Mixing.BALL_COLOR_YELLOW)
16        mixing.number = 10;
17    }
18}
19 
20Mixing{
21    id: mixing
22    onColorChanged: {
23        root.color = color
24        mixing.stop(color)
25    }
26 
27    Component.onCompleted:
28    {
29        console.log("default ball number is", number)
30    }
31 
32    onNumberChanged:
33    {
34        console.log("new ball number is", number)
35    }
36}

实现效果如下:

关于实现可以被QML访问的 C++ 类我们再给大家一个稍微复杂一点的例程,大家可以进行参考。

last.zip

因为篇幅问题,我们分为上下两个章节,小伙伴们可以移步下一章节哦-》-》-》


287

顶一下

刚表态过的朋友 (287 人)

相关阅读

微雪官网|产品资料|手机版|小黑屋|微雪课堂. ( 粤ICP备05067009号 )

GMT+8, 2025-4-19 02:28 , Processed in 0.017592 second(s), 13 queries .

返回顶部