QML与C++混合编程

 之前写的文章都是一段一段的,现在整合起来,预估又是一段长臭文...

零、前言

1、先看一下相关类的继承关系:

①、视图关系

 

 ②、QML加载相关

 

 上方图参考:https://blog.csdn.net/qq_34139994/article/details/105195447

 

2、关于Qt与QML的个人理解

一切的一切,底层都是C++实现的,我们通过Qt封装好的C++类,如视图、引擎等,去加载QML文件,底层C++读取这些QML文本后,动态实例化相关对象,最终呈现在界面上。

 

 一、在Qt中加载QML的方式

1、QQmlApplicationEngined搭配 Window

2、QQuickView 搭配 Item

3、QQuickWidget 加载 QML【Item】

见之前文章:https://www.cnblogs.com/judes/p/15606042.html

注意本文以Quick工程为例,非Widget工程,故选择的是前两种加载方式

 

二、传递C++类型/对象给QML使用

1、注册

 有非常多的注册模板函数可供使用,相关信息在手册里直接搜索qmlRegist..

 int qmlRegisterAnonymousType
 int qmlRegisterExtendedType
 int qmlRegisterExtendedUncreatableType
 int qmlRegisterInterface           //注册一个接口,不可被实例化,可以使用类型名引用【多态】
 void qmlRegisterModule
 int qmlRegisterRevision
 int qmlRegisterSingletonInstance
 int qmlRegisterSingletonType         //注册单例类型,可安全代替setContextProperty
 int qmlRegisterType              //注册一个可实例化对象,最常用
 int qmlRegisterTypeNotAvailable
 int qmlRegisterUncreatableMetaObject
 int qmlRegisterUncreatableType       //注册不可被实例化的对象,可以使用其中的枚举

举例:创建一个C++的Udp类,并注册给QML用

//cpp
qmlRegisterType<Myudp>("Myudp.module",1,0,"Myudp");

//qml
import Myudp.module 1.0
Myudp{
id:udp
}

 需要添加头文件:

#include <QtQml/qqmlengine.h>

 

2、设置上下文

三种加载方式使用的工具是:

QQmlApplicationEngined
QQuickView
QQuickWidget 

它们都具有一个函数rootContext,返回QQmlContext;

QQmlContext提供函数:setContextProperty【注册单个上下文属性】、setContextProperties【注册多个上下文属性】。

这个上下文属性其实就是在C++里实例化了的对象,效果与上面的注册单例qmlRegisterSingletonType一致。

举例:

//C++
Myudp udp;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("udp",&udp);//注册对象

//QML
udp.initUdpSlot()

 注意:需要先设置上下文,再连接qml:

 

 

三、封装C++对象

上面只是QML里能够用简单访问到C++对象,是不足以支持实际项目开发的,故需要对C++对象进行封装。

见之前的文章:https://www.cnblogs.com/judes/p/11242922.html

Qt提供了这些宏用于封装:

1、信号与槽
C++类中的信号与槽都可以在QML中访问

2、C++类的成员函数,Q_INVOKABLE
Q_INVOKABLE void function();

3、C++类的枚举,Q_ENUMS
Q_ENUMS (enumName)

4、C++的属性,Q_PROPERTY
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

 

1、例子:封装一个颜色生成器给QML使用,C++暴露属性、信号、槽、函数、枚举,QML使用【信号的使用有多种方式】

①、MyColor.h

#ifndef MYCOLOR_H
#define MYCOLOR_H

#include <QObject>
#include <QTimer>
#include <QtQml/qqmlengine.h>//注册函数需要
#include <QDebug>
#include <QColor>

class MyColor : public QObject
{
    Q_OBJECT
    //枚举暴露
    Q_ENUMS(RECT_COLOR)

    //属性暴露,QML可直接使用属性[动态绑定、获取、设置,前提是匹配了对应函数]
    Q_PROPERTY(QString color READ getColor NOTIFY colorChanged)
    Q_PROPERTY(QString info READ getInfo WRITE setInfo)

    //属性相关
public:
    explicit MyColor(QObject *parent = nullptr);
    inline QString getColor(){return m_cur_color;}

    inline QString getInfo(){return m_info;}
    inline void setInfo(QString info){m_info = info;qDebug()<<"info changed:"<<m_info;}

    inline void resetColor(){m_cur_index = 0;}
    inline static void RegisterType(){qmlRegisterType<MyColor>("MyColor.module",1,0,"MyColor");}

    //信号相关,QML可直接onSignal访问
signals:
    void colorChanged();

    //公共函数相关
public:
    Q_INVOKABLE inline void reSetColor(){m_cur_index = 0;}

    //槽函数相关,QML可直接访问,但是需要是public的,private访问会出错
public slots:
    inline void addColor(){
        QColor clr(rand() % 256, rand() % 256, rand() % 256);
        m_colors.append(clr.name());
    }

    //枚举
public:
    enum RECT_COLOR {
            RECT_COLOR_YELLOW,
            RECT_COLOR_RED,
            RECT_COLOR_BLUE,
            RECT_COLOR_ALL
        };

    //内部属性相关
private:
    QString m_cur_color;       //当前的颜色
    int m_cur_index = 0;       //颜色序号
    QList<QString> m_colors;   //存储所有颜色列表
    QTimer m_timer;            //定时器修改当前颜色

    QString m_info;            //模拟一个变量,QML可修改
};

#endif // MYCOLOR_H

②、MyColor.cpp

#include "mycolor.h"

MyColor::MyColor(QObject *parent)
    : QObject{parent}
{
    m_colors.append("red");
    m_colors.append("black");
    m_colors.append("green");
    m_colors.append("gray");
    m_colors.append("blue");

    QObject::connect(&m_timer, &QTimer::timeout, [this]{
        if(m_cur_index < m_colors.size())
        {
            m_cur_color = m_colors[m_cur_index++];
            emit colorChanged();
            qDebug()<<"C++ cur color:"<<m_cur_color;
        }
        else
        {
            m_cur_index = 0;
            m_cur_color = m_colors[m_cur_index++];
            emit colorChanged();
            qDebug()<<"C++ cur color:"<<m_cur_color;
        }
    });
    m_timer.start(1000);
}

③、main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "mycolor.h"

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

    QGuiApplication app(argc, argv);

    MyColor::RegisterType();

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

    return app.exec();
}

④、qml

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import MyColor.module 1.0

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

    MyColor {
        id: c1
        onColorChanged: {
            r2.color = c1.color //①.直接在信号对象里获取信号参数
        }
    }
    Connections {
        target: c1
        function onColorChanged() {
            r3.color = c1.color //②.通过信号槽连接器捕捉信号
        }
    }

    Row {
        anchors.fill: parent
        spacing: 10
        Rectangle {
            id: r1
            width: 100
            height: 100
            color: c1.color //③.直接使用属性,进行属性绑定
        }
        Rectangle {
            id: r2
            width: 100
            height: 100
            color: "pink"
        }
        Rectangle {
            id: r3
            width: 100
            height: 100
            color: "pink"
        }
        Button {
            text: "add color"
            onClicked: {
                c1.addColor()//④.直接调用C++对象的槽函数
            }
        }
        Button {
            text: "reset color"
            onClicked: {
                c1.reSetColor()//⑤.调用函数[C++里需要标记上Q_INVOKABLE]
            }
        }
        Button {
            text: "set time to c++"
            onClicked: {
                c1.info = Qt.formatDateTime(new Date(), "hh-mm-ss")//⑥.直接使用属性,进行属性设置
            }
        }
        Button {
            text: "get enum"
            onClicked: {
                console.log(MyColor.RECT_COLOR_YELLOW)      //⑦.直接使用枚举
            }
        }
    }
}

 

第一个rect通过属性绑定来设置颜色;

第二个rect通过在对象里捕捉信号,并设置颜色;

第三个rect通过Connections连接器来设置颜色;【注意后两个rect的属性未绑定,相当于我们收到信号再主动去设置颜色】

add color按钮直接调用C++的槽函数;

reset color按钮调用C++的普通函数,此函数需要被Q_INVOKABLE标记;

set time to c++按钮用于设置C++对象属性的值,这里设置的是当前时间,C++被设置后打印出来;

get enum按钮获取C++类里定义的枚举值,这里是直接打印,这里的使用方式是:类名.枚举值【如果注册的是对象该怎么用呢?】。

 

至此,这个例子是很全面的,描述了如何封装C++类,然后注册,然后QML如何访问。

完整下载例子:https://download.csdn.net/download/m0_53292003/63241808

 

2、例子2:封装UDP给QML使用

见之前文章:https://www.cnblogs.com/judes/p/11241576.html

 

四、QML里响应C++的信号

//C++
Class A{
  signals:
      void rcvData(QString str);     
};
//QML
onRcvData:{
  //执行x如console.log(str);
}

见之前文章:https://www.cnblogs.com/judes/p/11243242.html

 

五、C++访问QML对象的函数

这种访问常用于第三种加载方式,通过获取QML根对象,再通过元对象的invokeMethod函数访问对象的函数。

通过QMetaObject::invokeMethod,见之间文章:

 访问函数:https://www.cnblogs.com/judes/p/12939000.html

 传递List:https://www.cnblogs.com/judes/p/13029400.html

 

六、C++捕捉QML的信号

这种访问常用于第三种加载方式,通过获取QML根对象,再通过QObject::connect连接根对象的信号和C++的槽函数。

#include <QQuickItem>
//指定对象QObject* quitButton = root->findChild<QObject*>("quitButton");
if (quitButton){
    QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
}

见之前文章:https://www.cnblogs.com/judes/p/11243242.html

 

 

七、进阶之MVC编程

MVC编程也是QML与C++的混合编程,通常C++提供Model也就是数据,QML提供View也就是视图。见之前文章:

ListView:https://www.cnblogs.com/judes/p/13450897.html

MVC:https://www.cnblogs.com/judes/p/15622972.html

 

PS:

1、QML使用C++对象时,尽量注册C++类,而不要设置C++对象为上下文

https://raymii.org/s/articles/Qt_QML_Integrate_Cpp_with_QML_and_why_ContextProperties_are_bad.html

posted @ 2021-12-14 11:56  朱小勇  阅读(961)  评论(0编辑  收藏  举报