Qml和C++混合编程

  • 通过qml高效便捷的构建UI界面,使用C ++来实现业务逻辑和复杂算法
  • Qt中提供了两种在 QML 环境中使用C ++对象的方式
  • 第一种:在C ++中实现一个类,注册到qml环境中,qml环境中使用该类型创建对象
  • 第二种:在C ++中构造一个对象,将这个对象设置为qml的上下文属性,在qml环境中直接使用该属性

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

  • 必须满足两个条件:一是派生自QObject类或QObject类的子类,二是使用Q_OBJECT宏
  • 新建C++ Class

mixing.h

#ifndef MIXING_H
#define MIXING_H

#include 
#include 

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

signals:
    void colorChanged(const QColor & color);

public slots:   //槽必须被声明为public或protected
    void start();
};

#endif // MIXING_H

mixing.cpp

#include "mixing.h"
#include 

Mixing::Mixing(QObject *parent) : QObject(parent)
{
     

}

void Mixing::start()
{
     
    qDebug() << "start";
    emit colorChanged(Qt::blue);
}

  • 将Mixing类注册为qml类型—注册类型qmlRegisterType
    原型:
template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

模板参数typename ,即要实现的 C++ 类的类名。
第一个参数uri ,指定一个唯一的包名,一是用来避免名字冲突,二是可以把多个相关类聚合到一个包中方便引用。如我们常写这个语句 “import QtQuick.Controls 2.3” ,其中的 “QtQuick.Controls” 就是包名 uri
而2.3则是版本,是versionMajor=2和versionMinor=3的组合。
qmlName则是 QML中可以使用的类名。

main.cpp

#include 
#include 
#include 
#include "mixing.h"

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

    QGuiApplication app(argc, argv);

    //注册类型 注册动作一定要放在 QML 上下文创建之前,否则的话,注册是没有用的
    //Mixing类注册成为Qml类型Mixing,主版本是1,次版本是0,包名是an.qt.Mixing
    qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

  • qml导入Mixing类

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0

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

    MouseArea{
        anchors.fill: parent
        onClicked: {
            mixing.start()
        }
    }

    Mixing{
        id: mixing
        onColorChanged: {
            root.color = color
        }
    }
}
  • 结果

枚举类型

  • 在注册的类中使用枚举类型,可以使用Q_ENUMS 宏将该枚举注册到元对象系统中

mixing.h

#ifndef MIXING_H
#define MIXING_H

#include 
#include 

class Mixing : public QObject
{
     
    Q_OBJECT
    Q_ENUMS(BALL_COLOR)

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

    enum BALL_COLOR{
     
        BALL_COLOR_YELLOW,
        BALL_COLOR_BLUE,
        BALL_COLOR_GREEN,
    };

signals:
    void colorChanged(const QColor & color);

public slots:   //槽必须被声明为public或protected
    void start(BALL_COLOR ballColor);
};

#endif // MIXING_H

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0

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

    MouseArea{ //点击鼠标左键,窗口颜色变蓝;点击鼠标右键,窗口颜色变绿;双击鼠标,窗口颜色变黄
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton

        onClicked: {
            if(mouse.button === Qt.LeftButton)
            {
                mixing.start(Mixing.BALL_COLOR_BLUE)
            }else if(mouse.button === Qt.RightButton)
            {
                mixing.start(Mixing.BALL_COLOR_GREEN)
            }
        }

        onDoubleClicked: {
            mixing.start(Mixing.BALL_COLOR_GREEN)
        }
    }

    Mixing{
        id: mixing
        onColorChanged: {
            root.color = color
        }
    }
}

mixing.cpp

#include "mixing.h"
#include 

Mixing::Mixing(QObject *parent) : QObject(parent)
{
     

}

void Mixing::start(BALL_COLOR ballColor)
{
     
    QColor color;
    qDebug() << "start";
    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;
    default:
        break;
    }
    emit colorChanged(Qt::blue);
}

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

  • 在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,这个宏必须放在返回函数前面, 且在QML中访问的前提是public或protected成员函数
  • 定义属性需要使用 Q_PROPERTY宏,通过定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。使用 Q_PROPERTY 宏,定义的类必须是QObject的后代,必须在类首使用Q_OBJECT宏

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])

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

  • READ:读取属性值,如果没有设置MEMBER的话,它是必需的。一般情况下,函数是个const函数,返回值类型必须是属性本身的类型或这个类型的const引用,没有参数。

  • WRITE:设置属性值,可选项。函数必须返回void,有且仅有一个参数,参数类型必须是属性本身的类型或这个类型的指针或引用。

  • NOTIFY:与属性关联的可选信号。这个信号必须在类中声明过,当属性值改变时,就可触发这个信号,可以没有参数,有参数的话只能是一个类型同属性本身类型的参数,用来记录属性改变后的值。

实现

mixing.h

#ifndef MIXING_H
#define MIXING_H

#include 
#include 

class Mixing : public QObject
{
     
    Q_OBJECT
    Q_ENUMS(BALL_COLOR)
    //修饰了名为number的属性,number通过Number函数读得数据,通过setNumber函数写入数据,触发信号是Numberchanged函数
    Q_PROPERTY(unsigned int number READ Number WRITE setNumber NOTIFY Numberchanged)

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

    enum BALL_COLOR{
     
        BALL_COLOR_YELLOW,
        BALL_COLOR_BLUE,
        BALL_COLOR_GREEN,
    };
    unsigned int Number() const;
    void setNumber(const unsigned int & Number);
    Q_INVOKABLE void stop();

signals:
    void colorChanged(const QColor & color);
    void Numberchanged();

public slots:   //槽必须被声明为public或protected
    void start(BALL_COLOR ballColor);

private:
    unsigned int m_Number;
};

#endif // MIXING_H

mixing.cpp

#include "mixing.h"
#include 

Mixing::Mixing(QObject *parent) : QObject(parent)
{
     

}

void Mixing::start(BALL_COLOR ballColor)
{
     
    QColor color;
    qDebug() << "start";
    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;
    default:
        break;
    }
    emit colorChanged(Qt::blue);
}

unsigned int Mixing::Number() const
{
     
    return m_Number;
}

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

void Mixing::stop()
{
     
    qDebug() << "颜色改变ing...";
}

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0

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

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

        onClicked: {
            if(mouse.button === Qt.LeftButton)
            {
                mixing.start(Mixing.BALL_COLOR_BLUE)
            }else if(mouse.button === Qt.RightButton)
            {
                mixing.start(Mixing.BALL_COLOR_GREEN)
            }
        }

        onDoubleClicked: {
            mixing.start(Mixing.BALL_COLOR_GREEN)
            mixing.number = 10
        }
    }

    Mixing{
        id: mixing
        onColorChanged: {
            root.color = color
            mixing.stop(color)
        }
        Component.onCompleted:
        {
            console.log("default ball number is ", number)
        }

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

第二种 QML上下文属性设置

  • QQmlContext类
    定义了qml引擎内的上下文,上下文允许将数据暴露给由qml引擎实例化的qml组件
    每个QQmlContext包含一组属性,允许以名称将数据显式地绑定到上下文。通过调用QQmlContext::setContextProperty()来定义和更新上下文属性
void QQmlContext::setContextProperty(const QString &name, const QVariant &value)
简单的上下文属性,对应的值为QVariant类型。

void QQmlContext::setContextProperty(const QString &name, QObject *value)
相对来说稍微复杂一些,QObject*对象类型。

设置简单的上下文属性

  • 新建一个Qt quick工程

main.cpp

#include 
#include 
#include 
#include 

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

    QGuiApplication app(argc, argv);

    QQuickView view;
    view.rootContext()->setContextProperty("Data", QString("设置Qml上下文属性"));
    view.setSource(QUrl(QStringLiteral("qrc://main.qml")));
    view.show();

    return app.exec();
}

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2

Rectangle { //main.cpp已经view.show(),所以不需要在Window对象下定义控件
    visible: true
    width: 640
    height: 480
    color: "lightgray"

    Text {
        id: text
        anchors.centerIn: parent
        text: Data
    }
}

设置对象为上下文属性

main.cpp

#include 
#include 
#include 
#include 
#include "mixing.h"

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

    Mixing mixing;
    QQuickView view;
    view.rootContext()->setContextProperty("mixing", &mixing);
    view.setSource(QUrl(QUrl(QStringLiteral("qrc://main.qml"))));
    view.show();

    return app.exec();
}

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2

Rectangle {
      //main.cpp已经view.show(),所以不需要Window对象下定义控件
    id: root
    visible: true
    width: 640
    height: 480

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

            onClicked: {
     
                mixing.start()
                mixing.number = 10
            }
        }

        Connections{
     
            target: mixing
            onColorChanged: {
     
                root.color = color
                mixing.stop(color)
            }

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

  • 因为去掉了qmlRegisterType() 调用,所以在 main.qml中不能再访问Mixing类了,比如说不能通过类名来引用它定义的BALL_COLOR枚举类型了,即枚举类型的值不可以调用

Mixing.cpp修改start函数 以及 Mixing.h的start函数去掉形参

void Mixing::start()
{
     
    //QColor color;
    qDebug() << "start";
//    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;
//    default:
//        break;
//    }
    emit colorChanged(Qt::blue);
}

C++ 访问qml的属性、函数、信号

  • 以上介绍了在qml中访问 C++ 中属性和方法。反过来,在 C++ 中加载qml文件可以用QQmlComponent或QQuickView,然后就可以在 C++ 中访问Qml对象。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。

QQmlComponent在C++中访问qml中的属性

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0

Window {
    id: root
    visible: true
    width: 640
    height: 480

    Rectangle {	//
        objectName: "rect" //这个值是为了在C++中能够找到这个Rectangle
        anchors.fill: parent
    }

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

            onClicked: {
                mixing.start()
                mixing.number = 10
                qmlSignal("这是qml文件中的qml信号")
            }
        }

        Mixing{
            id:mixing
        }
}

main.cpp

#include 
#include 
#include 
#include "mixing.h"

int main(int argc, char *argv[])
{
     
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");

    QQmlEngine engine;
    QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml")));
    QObject *object = compontext.create();
    qDebug() << "width value is " << object->property("width").toInt(); //读取width属性值
    object->setProperty("width", 320); //修改width属性值
    qDebug() << "height value is " << QQmlProperty::read(object, "height").toInt(); //读取height属性值
    QQmlProperty::write(object, "height", 240); //修改height属性值
    QObject *rect = object->findChild<QObject*>("rect");
    if(rect)
    {
        //查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色
        rect->setProperty("color", "orange");
    }

    return app.exec();
}

在C++中访问qml中的函数与信号

  • C++中,使用 QMetaObject::invokeMethod() 可以调用QML中的函数,它 是个静态方法,函数原型:

    bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) [static]

    • 它的返回值如果为true则表明调用成功,返回false,则说明参数不正确或被调用函数名错误。重点关注它的前四个参数。
      第一个参数是被调用对象的指针
      第二个参数是方法名字
      第三个参数是连接类型
      第四个参数用来接收返回值
    • 必须使用Q_ARG() 宏来声明函数参数,用Q_RETURN_ARG() 宏来声明函数返回值。
    • 传递给被调用方法的参数
      QGenericArgument Q_ARG(Type, const Type & value)
    • 返回类型
      QGenericReturnArgument Q_RETURN_ARG(Type, Type & value)
  • 信号传递到 C++ :使用 QObject::connect() 可以连接QML中的信号,connect()共有四个重载函数,它们都是静态函数。必须使用SIGNAL()宏来声明信号,SLOT()宏声明槽函数

  • QObject::disconnect() 可以解除信号与槽函数的连接


main.qml 添加一个方法和信号

import QtQuick 2.9
import QtQuick.Window 2.2
import an.qt.Mixing 1.0

Window {
    id: root
    visible: true
    width: 640
    height: 480

    signal qmlSignal (string message)   //单击会输出message的值
    onQmlSignal: console.log("这是一个信号:", message)

    function qmlFunction(parameter)
    {
        console.log("这是一个方法", parameter)
        return "function from qml"
    }

    Rectangle {
        objectName: "rect" //这个值是为了在C++中能够找到这个Rectangle
        anchors.fill: parent
    }

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

            onClicked: {
                mixing.start()
                mixing.number = 10
                qmlSignal("这是qml文件中的qml信号")
            }
        }

        Mixing{
            id:mixing
        }
}

main.cpp 调用信号和方法

#include 
#include 
#include 
#include "mixing.h"

int main(int argc, char *argv[])
{
     
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing");

    QQmlEngine engine;
    QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml")));
    QObject *object = compontext.create();
    qDebug() << "width value is " << object->property("width").toInt(); //读取width属性值
    object->setProperty("width", 320); //修改width属性值
    qDebug() << "height value is " << QQmlProperty::read(object, "height").toInt(); //读取height属性值
    QQmlProperty::write(object, "height", 240); //修改height属性值
    QObject *rect = object->findChild<QObject*>("rect");
    if(rect)
    {
        //查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色
        rect->setProperty("color", "orange");
    }

    QVariant returnedValue, message = "Hello from c++";
    QMetaObject::invokeMethod(object, "qmlFunction",
                              Q_RETURN_ARG(QVariant, returnedValue),
                              Q_ARG(QVariant, message));
    qDebug() << "returnedValue is " << returnedValue.toString();
    Mixing mixing;
    QObject::connect(object, SIGNAL(qmlSignal(QString)),
                     &mixing, SLOT(cppSlot(QString)));

    return app.exec();
}

mixing.h中定义cppSlot槽函数

public slots:   //槽必须被声明为public或protected
    void start();

    void cppSlot(const QString &message)
    {
     
        qDebug() << "这是一个qml信号:" << message;
    }
  • 效果

你可能感兴趣的:(QT,c++,qt,qml)