QML_Qml和C++混合编程

QML_Qml和C++混合编程

前言
最后这几节我们要给大家讲一下如何使用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元对象系统提供的内容,位置一般在语句块首行。
信号和槽
只要是信号和槽,都可以在 QML 中访问,可以把 C++ 对象的信号连接到 QML 中定义的方法上,也可以把 QML 对象的信号连接到 C++ 对象的槽上,还可以直接调用 C++ 对象的槽或信号……所以,这是最简单好用的一种途径。

myobject.h说明
1、 MyObject类中的信号colorChanged()和槽函数start都可以被Qml访问,但是注意槽必须被声明为public或protected,而且信号在 C++ 中使用时要用到emit关键字,但是在Qml中就是个普通的函数。我们想要实现的效果是点击鼠标,改变窗体颜色,看一下信号和槽是如何在Qml和 C++中传递的。
2、如果想要在注册的类中使用枚举类型,可以使用Q_ENUMS 宏将该枚举注册到元对象系统中。
3、C++ 类的属性和成员函数:
在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,但是注意的是在QML中访问的前提是public或protected成员函数,而且这个宏必须放在返回函数前面。而定义属性则需要使用Q_PROPERTY 宏,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。要想使用 Q_PROPERTY 宏,定义的类必须是QObject的后裔,必须在类首使用Q_OBJECT宏
4、Q_PROPERTY宏的原型:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
5、属性的type、name是必需的,其它是可选项,最常用的有READ、WRITE、NOTIFY。属性的type可以是QVariant支持的任何类型,也可以是自定义类型,包括自定义类、列表类型、组属性等。另外,属性的READ、WRITE、RESET是可以被继承的,也可以是虚函数,这些特性并不常用。
READ:读取属性值,如果没有设置MEMBER的话,它是必需的。一般情况下,函数是个const函数,返回值类型必须是属性本身的类型或这个类型的const引用,没有参数。
WRITE:设置属性值,可选项。函数必须返回void,有且仅有一个参数,参数类型必须是属性本身的类型或这个类型的指针或引用。
NOTIFY:与属性关联的可选信号。这个信号必须在类中声明过,当属性值改变时,就可触发这个信号,可以没有参数,有参数的话只能是一个类型同属性本身类型的参数,用来记录属性改变后的值。
6、通过Q_INVOKABLE修饰了stop函数。通过Q_PROPERTY修饰了名为number的属性,number通过getNumber函数读得数据,通过setNumber函数写入数据,触发信号是Numberchanged函数。在cpp文件中写这几个函数的内容。

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>

class MyObject : public QObject
{
    Q_OBJECT
    Q_ENUMS(BALL_COLOR)
    Q_PROPERTY(unsigned int number READ getNumber WRITE setNumber NOTIFY Numberchanged)

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

    enum BALL_COLOR {  //定义枚举必须使用Q_ENUMS(BALL_COLOR),这样才能在别的文件夹调用
        BALL_COLOR_YELLOW,
        BALL_COLOR_BLUE,
        BALL_COLOR_GREEN,
    };
    unsigned int getNumber() const;
    void setNumber(const unsigned int &Number);
    Q_INVOKABLE void stop(QString str);  //使用这个Q_INVOKABLE宏定义之后,可以直接在别的文件夹调用

signals:  //定义信号
    void colorChanged(const QColor &color);
    void Numberchanged();

public slots:  //定义槽函数
    void start(BALL_COLOR ballColor);

private:  
    unsigned int m_Number;

};

#endif // MYOBJECT_H

myobject.cpp说明
实现函数方法

#include "myobject.h"
#include <QDebug>  
#include "qcolor.h"


MyObject::MyObject(QObject *parent) : QObject(parent)
{

}

unsigned int MyObject::getNumber() const
{
    return m_Number;
}

void MyObject::setNumber(const unsigned int &number)
{
    if (number != m_Number) {
        m_Number = number;
        emit Numberchanged();
    }
}

void MyObject::stop(QString str)
{
    qDebug() << str;
}

void MyObject::start(BALL_COLOR ballColor)
{
    QColor color;
    qDebug() << "start" << ballColor;
    switch (ballColor) {
        case BALL_COLOR_BLUE:
            color = Qt::blue;
            break;
        case BALL_COLOR_GREEN:
            color = Qt::green;
            break;
        case BALL_COLOR_YELLOW:
            color = Qt::yellow;
            break;
    }
    emit colorChanged(color);
}

main.cpp说明
1、这里传递了一个颜色到Qml中,当然,现在我们肯定还无法在qml文件中使用MyObject类,那么如何将MyObject类注册为Qml类型呢?其实有很多办法,我们这里就举一个最常规的注册类型qmlRegisterType。它比较常见的原型:
int qmlRegisterType<类名>(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
它的第一个参数uri ,让你指定一个唯一的包名,类似Java 中的那种,一是用来避免名字冲突,二是可以把多个相关类聚合到一个包中方便引用。比如我们常写这个语句 “import QtQuick.Controls 2.3” ,其中的 “QtQuick.Controls” 就是包名 uri ,而2.3则是版本,是versionMajor和versionMinor的组合。 qmlName则是 QML中可以使用的类名。
2、我们把MyObject类注册成为Qml类型MyObject,主版本是1,次版本是0,包名是myobject。注意:注册动作一定要放在 QML 上下文创建之前,否则的话,注册是没有用的。
接下来我们就可以在qml文件中导入MyObject类,并且使用它了。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "myobject.h"

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

    QGuiApplication app(argc, argv);

/*函数原型int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)*/
    qmlRegisterType<MyObject>("myobject", 1, 0, "MyObject");//最常规的注册类型qmlRegisterType

    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);
    engine.load(url);

    return app.exec();
}

main.qml说明
1、 在qml文件中导入MyObject类(import myobject 1.0),并且使用它
2、注册了枚举类型之后,在Qml中就可以用(类名.枚举值)的形式来访问了,调用枚举类型的方法形如MyObject.BALL_COLOR_BLUE,注意前面是类名哦,不是设置的id。我们这次要实现的效果是点击鼠标左键,窗口颜色变蓝;点击鼠标右键,窗口颜色变绿;双击鼠标,窗口颜色变黄。
3、在Qml中调用函数和属性了,打开界面什么都不做时,会输出number的初始值,因为没有为其初始化,所以大家返回的数据可能不一定为0。双击时,会设置number的值,这里我们设置的是20,也可以通过setNumber来写数的。当number的值改变,会触发Numberchanged信号,发射出去。所以还需要在Qml中写一个信号处理函数,也是输出number的值,不过现在输出的就是改变之后的number了。

import QtQuick 2.9
import QtQuick.Window 2.0
import QtQuick.Controls 2.5
import myobject 1.0

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

    MyObject {
        id: tg
        onColorChanged: {
            window.color = color
            tg.stop("改变颜色")
        }

        Component.onCompleted: {
            console.log("default ball number is", number)
        }

        onNumberChanged: {
            console.log("new ball number is", number)
        }
    }

    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton

        onClicked: {
            //tg.start()
            if (mouse.button === Qt.LeftButton) {
                tg.start(MyObject.BALL_COLOR_BLUE)
                //tg.number = 5
            } else if (mouse.button === Qt.RightButton) {
                tg.start(MyObject.BALL_COLOR_GREEN)
                tg.number = 6
            }
        }

        onDoubleClicked: {
            tg.start(MyObject.BALL_COLOR_YELLOW)
            tg.number = 20
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值