Qt QML 与 C++ 交互 信号与槽 属性 注册等初学者手册

前言

  QML 是一套 QT 开发前端显示的软件,其最大特色就是基于 Qt 的宏系统实现了与 C++ 及 Javascript 交互和连接,本篇主要讲解 QML 与 C++ 的交互( QML 调用 C++),至于 C++ 调用 QML 部分相对比较简单查看官方文档即可
  很多 C++ 与 QML 以及 Javascript 的变量,宏系统会自动进行切换

其他内容:
Javascript 动态生成 QML 对象
QML Chart 使用
QML 动画效果讲解


目录结构

官方文档 Official Documentation  If you need English documentation, you can check the official documentation

一、信号和槽
主要简单介绍了Qt信号
二、QML调用 C++ 的各种办法以及要完成调用的过程
注意:只要了解 1 和 2 就已经掌握了 QML 与 C++ 交互的关键
 1. C++ 类注册 & QML 加载方式 & 不同注册的区别
 2. 各种调用方式
  1) 信号与槽
  2) C++ 定义可被调用的属性
  3) QML 中实例化 C++ 子类(一个或多个,以及一种宏定义)
  4) 以结构体方式调用子类
  5) 定时器方式定时动态更新变量值
  6) QML 下控制 C++ 实例化先后顺序
  7) 版本控制
  8) 为 C++ 拓展附属性


补充
1)QML 自动绑定
Item {
	property var val : 30
	Text {
		text: val
	}
}

//例 2
Item {
	property var val : 30
	Text {
		id: t1
		text: val
	}
	Component.onCompleted: {
		t1.text = 1
	}
}

//例 3
Item {
	property var val : 30
	Text {
		id: t1
	}
	Binding {
		target: t1
		property: "text"
		value: val
		restoreMode: Binding.RestoreBinding
	}
	Component.onCompleted: {
		t1.text = 1
	}
}

这样 Text 的 属性 text 就自动与 val 变量绑定在一起了,但是如果执行 例2 操作之后,就会自动解绑,那这种时候该怎么弄?
使用 Binding 如 例3 就可以确保绑定不会断开,其中 restoreMode 有好几种模式可以自行查看


一、信号和槽

类似信号通讯处理机制,一方发出信号,指定一个或多个接收方用于处理任务,是异步的
如果不了解常见的信号通讯处理机制,可以理解为在程序的之上还有一层处理机制,信号和槽对应的函数均提交到顶层数组之中等待触发和调配。

满足以下条件既可以用   
1 object 子类
2 注明 Q_OBJECT
3 信号用 signals: 槽用 private/publice slots:
4 连接 connect();

除了能够处理异步通信之外,最大优点就是可以确保线程安全
例程 xxx.h
#include <QObject>
#include <QDebug> //Qt 特有的打印调试信息工具

class Test : public QObject
{
    Q_OBJECT
public:
    explicit Test() {
        // connect(this, SIGNAL(send2Target()), this, SLOT(rece2Signal())); // Qt4 写法 this --> 可替换对应 实例化变量的指针
        connect(this, &Test::send2Target, this, &Test::rece2Signal);
        
        qDebug() << "I sent out a signal";
        emit send2Target(); //emit 是触发信号的方式,这是比较标准的写法,让阅读者可以理解这是一个信号,而非处理函数
        //  send2Target(); //  也可以发送信号,但是不建议如此,难以区分函数的类型和作用
        
private slots: //槽 public slots / pravite slots 均可,作用范围取决于前缀
        void rece2Signal() {
            qDebug() << "I got a signal";
        }
    
};

二、 QML 调用 C++ 类的各种方法

注意:QML 使用的几个注意事项
1、QML 文件必须以类名形式命名文件,首字母大写 如 Test.qml
2、QML 文件相互之间不需要通过 import 导入,前提在同一目录(非同一目录需要 import “目录名称”)
3、QML 文件需要导入 xxx.qrc 资源文件件统一管理较为合适(取决于启动方式)

  1. QUrl::fromLocalFile(“xxx.qml”); 直接去寻找本地目录
  2. QUrl(QStringLiteral(“qrc:/xxx.qml”);//检索资源目录
  3. 其他方式请翻看官方文档说明

4、比如在 Test1.qml 中调用 同目录下的 Test2.qml ,只需要 Test2 { } 即可实例化,与文件名一致(别名等其他请翻看官方说明)

第一步:注册 / 实例化 (包括 2 种 C++ 启动 QML 方法)####

QML 需要通过 QML 引擎加载并实现

1. 通过自主实例化后,再加载
例程  xxx.cpp //如果提示模块找不到 请在 .pro 文件 Qt += qml quick 添加模块
#include <QQuickView>
#include <QGuiApplication>
#include <QQmlEngine>
#include <QQmlContext> 
#include "test.h"

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    
    Test test;
    /***************** 老版本方式 *************************/
//    QQuickView view;  // C++ 启动 QML 方式一 
//    view.engine()->rootContext()->setContextPrroperty("MyTest", &test);
//    view.setSource(QUrl(QStringLiteral("qrc:/main.qml"))); //main.qml 已经提前添加到 xxx.qrc QT 特有的资源文件中[百度 Qt 创建资源文件]
//    view.show();
    
    /**************** 新版本方式 ************************/
    
    QQmlApplicationEngine engine;  // C++ 启动 QML 方式一 
    engine.engine()->rootContext()->setContextPrroperty("MyTest", &test);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); //main.qml 已经提前添加到 xxx.qrc QT 特有的资源文件中[百度 Qt 创建资源文件]
      
    return app.exec();
}
/***************************main.qml**********************************************?
import QtQuick 2.13

Item {
    visible: true
    width: 200
    height: 200
    
    Component.onCompleted: {
            MyTest.send2Target()
            MyTest.rece2Signal()  //注意:必须 public slots 或者 Q_INVOKEABLE 函数(后面细说)
    }
}

2. 通过注册器注册
例程 xxx.Cpp //QML 启动方式二 在创建 QML 类型工程时自动生成的
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "test.h"

int main(int argc, char *argv[]) 
{
    qmlRegisterType<Test>("TestType", 1, 0, "Test");// C++ 类名,主版本号,次版本号,QML 调用类名
    
    QGuiApplication app(argc, argv);
    
    QQmlApplicationEngine engine;  //C++ 启动 QML 方式二 (官方推荐)
    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); // Qt::QueuedConnection 是 connect 的连接方式,其他方式翻看官方文档
        
    engine.load(url);
    
    
    return app.exec();
}

/***************************** main.qml ********************************/
import QtQuick 2.13
import TestType 1.0

Item {
    visible: true
    width: 200
    height: 200
    
    Test {
        id: testItem
        Component.onCompleted: {
        send2Target()
        rece2Signal()  //注意:必须 public slots 或者 Q_INVOKEABLE 函数(后面细说)
    }
    
    /**************** 如下也行 *****************/
    Component.onCompleted: {
        testItem.send2Target()
        testItem.rece2Signal()
    }
}


说明一: QML 能够调用的 C++ 元素必须是声明到宏中 比如通过注册器进行声明
说明二: 注册器类别
注册器分为://更多请查看官方文档
1、实例化注册:(可在 QML 实例化并调用)
通常使用 qmlRegisterType(“TestType”, 1, 0, “Test”);
2、声明注册
1)qmlRegisterType() //可以让继承 Test 的子类可以被实例化(用于声明父类)
2)qmlRegisterInterface(“Test”) //声明接口,当有子类被分配到直接接口,QML 会自动通过接口去调用
3)qmlRegisterUncreatableType(“Test”, 1, 0, “Test”, “can not instantiable”); //主要用于声明:附属属性和枚举(enum)的类
4)qmlRegisterSingletonType(“Test”, 1, 0, “Test”,singletontype_provider) //只允许实例化一次

singleton 需要定义的 //详见官方文档
static QJSValue singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) {
    Q_UNUSED(engine)
    
    static int seedValue = 5;
    QJSValue example = scriptEngine -> newObject();
    example.setProperty("Test", seedValue++);
    return example;
}

除以上介绍之外还有很多其他类型,可参靠官方说明
比如某些条件不允许实例化的 qmlRegisterTypeNotAvailable()


第二步、实现各种可别调用的类型
1. 信号和槽

在 QML 中定义信号 signal clickButton();
默认会有一个槽是 onClickButton 自动与之相关联

file: main.qml
import QtQuick 2.13
import QtQuick.Window 2.12

Window {
    id: window
    visible: true
    width: 200
    height: 200
    title: qsTr("hello world") //qsTr 是为了后续本地化做服务,类似 Qt 下的 tr("") 
    
    signal clickForTest()
    
    Text {
        id:testText
        text: qsTr("text")
    }
    
    MouseArea {
        anchors.fill: parent
        onClicked: clickForTest()
    }
    
    onClickForTest: testText.text = "OK"; 
}

//点击窗口可以查看到 text 变成 OK 说明成功

同理如果在 C++ 类中声明的 信号 和 槽 在类实例化后也可以同样这么使用

以两种槽才可被 QML 调用
1 使用 public slots: 关键字下声明
2 在 public 关键字下声明,并且在函数之前 Q_INVOKABLE void testSlot();

file: test.h
#include <QDebug>
#include <QObject>

class Test : public QObject 
{
        Q_OBJECT
public:
        explicit Test() {}
    
        Q_INVOKABLE void testSlot1() { qDebug() << "I am testSlot1";} //等价在 public slots: 下声明

public slots:
        void testSlot2() { qDebug() << "I am testSlot2";}
    
signals:
        void testSignal();
};
/*------------------------------file: main.cpp--------------------------------------------------*/
#include <QGuiApplication>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[] ) {
        qmlRegisterType<Test>("Test", 1, 0, "Test");
    
        QGuiApplication app(argc, argv):
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml");
        engine.load(url);
    
        return app.exec();
}
/*--------------------------------file: main.qml ----------------------------*/
import QtQuick 2.13
import QtQuick.Window 2.12
import Test 1.0

Window {
    id: window
    visible: true
    width: 200
    height: 200
    title: qsTr("hello, world!")
    
    Test {
        id: test
        onTestSignal: consloe.log(" I am testSignal")
    }
    
    MouseArea { 
        anchors.fill: parent
        onClicked: {
            test.testSignal() //发送信号
            test.testSlot2() //直接调用函数 类似 QML 发送testSlot2() 信号到 onTestSlot2() 一样
            test.testSlot1() // 同上,只是为了验证 Q_INVOKABLE 的效果
        }
    }
}

以上就是,信号和槽在 QML 的调用办法,接下来讲一下在 QML 中如何绑定特定信号与槽

  1. 使用 javascript 环境下使用 .connect / .disconnect 连接 / 断开
  2. 使用 Connections 框架

优势:
Connecting to targets not defined in QML 也就是可以连接没有定义在当前 QML 的对象信号

QML 中槽也就是执行函数,就是 JavaScript 函数

详细介绍文章

/*
方法一: 使用 .connect 
file: main.qml
分别演示:
1 同范围
2 不同范围
3 一信号对多个槽
4 与 C ++ 信号 及 槽连接
*/
import QtQuick 2.13
import QtQuick.Window 2.12
import Test 1.0 

Window {
    id: window
    visible: true
    width: 500
    height: 500
    title: qsTr("hello world !");
    
    signal send2Slot1()
    Test {
        id: test
        onSend2Target: consloe.log("I got a signal from send2Target");  //QML 默认信号连接
    }
    
    MouseArea {
        signal send2Slot2()
        
        id: mouseTest
        anchors.fill: parent
        onClicked: {
            send1Slot1()
            test.send2Target()
            send2Slot2()
        }
    }
    //Component.onCompleted 能够提供完整的 javascript 运行环境
    Component.onCompleted: {
        mouseTest.send2Slot2.connect(test.testSlot2); // QML 信号 连接 C++ 槽
        mouseTest.send2Slot2.connect(testSlot3); //不同定义范围连接
        send2Slot1.connect(test.testSlot1); 
        send2Slot1.connect(testSlot4); //一个信号对多个槽
        test.send2Target.connect(testSlot3); // C++ 信号 连接 QML 槽
    }
    
    function testSlot3() {
        consloe.log("I am testSlot3");
    }
    
    function testSlot4() {
        consloe.log("I an testSlot4");
    }    
    
}

/*
方法二: Connections ( If you need some properties, the function needs to import QtQml 2.3) 
类似 enabled 属性需要导入 QtQml 2.3 否则默认 enabled 为导通,如不需要这类属性可不导入 QtQml 2.3
特性与 方法一类型,不作另外说明和演示,只对其最大优势项进行说明
*/
/*------ main.qml ------*/
import QtQuick 2.13
import QtQuick.Window 2.12
import "subDir" //导入子目录,演示位于不同目录该怎么调用,记住 QML 文件首字母必须大写

Window {
    id: window
    visible: true
    width: 500
    height: 500
    title: qsTr("Hello world")
    
    signal send2Slot2()
    
    MouseArea {
        anchors.fill: parent
        onClicked: {
            send2Slot2()
        }   
    }
    
    Test1 {  //这样就实例化 Test1
    
    }
}

/*------------------ subDir/Test1.qml --------------------*/
import QtQuick 2.0

Item {
    Test2 {  //同一目录则不需要特别说明
    
    }
}

/*------------------ subDir/Test2.qml -------------------*/
import QtQuick 2.0

Item {
    Connections {
        target: window    //信号来源
        onSend2Slot1: {
            consloe.log("I got a signal from window");
        }
        enabled: true // true or false, 使用这个属性需要导入 import QtQml 2.3 更多请查看官方文档
    }
}

以上是信号与槽相关的内容及绑定实例


2、在 C++ 中定义一个属性

一个标准的 QML 属性应该执行 3 种操作:读、写、通知引用方同步修改
继承于 QObject
通过 Q_PROPERTY() 声明

在这里大家可以看出来,所有 C++ 定义的东西要想被 QML 调用就必须声明并且提交到 宏系统 之中,通过借助一些宏定义函数就能够实现这些功能,比如枚举 Q_ENUM() / Q_CLASSINFO() / Q_ENUM_NS() / Q_DECLARE_METATYPE()

具体如下

// file: piechart.h
#include <QObject>
#include <QQuickPaintedItem>

class PieChart : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    
public:
    explicit PieChart(QQuickItem *parent = nullptr); // 老版本支持使用 NULL / 0,规范是 nullptr 
    ~PieChart();
    
    QString name() const;
    void setName(const QString &name);
    
signals:
    void nameChanged();
private:
    QString m_name;
};

//file: piechart.cpp
#include "piechart.h"
#include <QPainter>

PieChart::PieChart(QQuickItem *parent) : QQuickPaintedItem (parent) {}
PieChart::~PieChart() {}

QString PieCharts::name() const {
    return m_name;
}

void PieCharts::setName(const QString &name) {
    if (name == m_name) //这步很重要,按照官方说明可以避免不必要的修改和避免进入死循环
        return;
    m_name = name;
    emit  nameChanged();
}

这个是 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])

简单说明:
(QString name READ name WRITE setName NOTIFY nameChanged) 官方推荐命名方式
QString name // 类命 属性名 //属性名:在 QML 使用时的表示
READ name // 返回 name 值的函数
WRITE setName //修改 name 值的函数
NOTIFY nameChanged //通知 QML 修改显示的信号,通常放在 setName 函数内

伪代码 在 QML 种调用 

PieChart {
    name: "xxxx"
}

这边的属性不一定都需要,如果只读或者 QML 不存在引用这个变量的情况,都可以把 WRITE 和 NOTIFY 去掉

至此,基本能够自如的操作 QML 和 C++ 之间的交互,下面内容着重介绍一些特殊的调用方法


3、调用子类 直接在 C++ 实例化子类

直接上代码就一目了然(此为官方代码的完善版,官方只有简介)
方式一: 一次只能创建或调用一个

/************** PieChart **************8/
//file: PieChart.h
#include <QObject>
#include <QQuickPaintedItem>
#include "pieslice.h"
#include <QPen>

class PieChart : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(PieSlice* pieSlice READ pieSlice WRITE setPieSlice  )
    
public:
    explicit PieChart(QQuickItem *parent = nullptr):QQuickPaintedItem(parent){ }
    QString name() const { return m_name;}
    void setName(const QString &name) {
        if (name != m_name) {
            m_name = name;
            emit nameChanged();
        }
    }
    
    QColor color() const { return m_color; }
    void setColor(const QColor &color) {
        if (color != m_color) {
            m_color = color;
            emit colorChanged();
        }
    }
    
    void paint(QPainter *painter) {
        QPen pen(m_color, 2);
        painter->setpen(pen);
        painter->setRenderHints(QPainter::Antialiasing, true); //抗锯齿
        painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16);
    }   
    
    PieSlice *pieSlice() const { return m_pieSlice; }
    void setPieSlice(PieSlice *pieSlice) {
        if (pieSlice == nullptr)
            return;
       m_pieSlice = pieSlice;
       pieSlice->setParentItem(this);     
    }
    
signals:
    void nameChanged();
    void colorChanged();
    
private:
    QString m_name;
    QColor m_color;
    PieSlice *m_pieSlice;
};

//file: pieslice.h
#include <QObject>
#include <QQuickPaintedItem>
#include <QPen>
class PieSlice: public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ color WRITE setColor)
public:
    explicit PieSlice(QQuick *parent = nullptr):QQuickPaintedItem(parent) {}
    
    QColor color() const;
    void setColor(const QColor &color) {
        if (color == m_color)
            return;
        m_color = color;
    }
    
    void paint(QPainter *painter) {
        QPen pen(m_color, 2);
        painter->setPen(pen);
        painter->setRenderHints(QPainter::Antialiasing, true);
        painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90*16, 290*16);
    }
private:
    QColor m_color;
};
//file: main.cpp

qmlRegisterType<PieSlice>("Charts", 1, 0, "PieSlice");
qmlRegisterType<PieChart>("Charts", 1, 0, "PieCharts");

//QML
import QtQuick 2.13
import Charts 1.0

Item {
    width: 200
    height: 200
    visible: true
}

PieChart {
    anchors.centerIn: parent
    width: 100; height: 100
    pieSlice: PieSlice {
        width:10; height: 10;
        color: "red"
    }
}
    

方式二: 一次性创建多个
注意:基于方式一程序修改, “+” 表示新增语句

//file: PieChart //QQmlListProPerty 详情请查看官方文档
+ Q_PROPERTY(QQmlListProperty<PieSlice> slices READ slices)
pulic:
+ QQmlListProperty<PieSlice> slices() {
    return QQmlListProperty<PieSlice>(this, nullptr, &PieChart::append_slice, nullptr, nullptr, nullptr);
    //分别代表 objcete, data, 新增函数,返回总数函数,检索函数,清空函数
}

private:
+ static void append_slice(QQmlListProperty<PieSlice> *list, PieSlice *slice) {
    PieChart *chart = qobject_cast<PieChart *>(list -> object); //列表所在的object是固定的也就是 PieChart 的标号,所以直接转换即可得到对应的标识
    if (chart) {
        slice -> setParentItem(chart);
        chart->m_slices.append(slice);
    }
}
+ QList<PieSlice *> m_slices;

//file:QML  中 PieChart 改为如下
PieChart {
    anchors.centerIn: parent
    width: 100; height: 100;
    
    slices: [
        PieSlice {
            width:10; height: 10
            color: "blue"
        },
        PieSlice {
            x: 10; 
            width: 10; height: 10;
            color: "green"
        }
    ]
}

拓展: 如果声明中增加如下语句,方式二中 QML 写法可以修改

//file: piechart.cpp
+ Q_CLASSINFO("DefaultProperty", "slices") //注意:DefalutPeroperty 是关键字不可改,而 slices 则与上文代码声明一致
//file: qml 可以改为
PieChart {
    anchors.centerIn: parent
    width: 100; height: 100;
    

    PieSlice {
        width:10; height: 10
        color: "blue"
    }
    PieSlice {
        x: 10; 
        width: 10; height: 10;
        color: "green"
    }  
}
//这部分代码等价与 方式二,但是如果没有声明 Q_CLASSINFO() 这样写,对程序而言,它会独立创建 2 个 PieSlice 但不属于同一个
PieChart 的子类,可以通常 debug 调试测试,不会条用 append_slice 这个函数

4、以结构体的形式修改子类变量

实现如下:
xxx.name : “dxxx”
xxx.email : “dddd”

代码跟 chapter 3 差不多,前提已经实例化一个子类罢了

//file: mymessage.h
#include <QObject>
#include "messageauthor.h"

class MyMessage: public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageAuthor *author READ author WRITE setAuthor NOTIFY authorChanged)
    
public:
    explicit MyMessage(QObject *parent = nullptr): QObject(parent), m_author(new MessageAuthor(this))
    {}
    
    ~MyMessage() {delete m_author;}
    
    MessageAuthor *author() const () { return m_author; }
    void setAuthor( MessageAuthor *author) {
        m_author-> setName(author->name());
        m_author-> setEmail(author->email());
        emit authorChanged();
    }
    
signals:
    void authorChanged();
    
private:
    MessageAuthor *m_author;
};

//file: messageauthor.h
#include <QObject>

class MessageAuthor: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged())
    Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged())
    
public:
    explicit MessageAuthor(QObject *parent = nullptr):QObject(parent) {}
    
    QString name () const { return m_name;}
    QString email() const { return m_email;}
    
    void setName(const QString &name) {
        if (name == m_name)
            return ;
        m_name = name;
        emit nameChanged();
    }
    void setEmail (const QString &email) {
        if (email == m_email)
            return;
        m_email = email;
        emit emailChanged()'
    }
    
signals:
    void nameChanged();
    void emailChanged();
    
private:
    QString m_name;
    QString m_email;
};

//file:main.cpp
qmlRegisterType<MyMessage>("MyMessage", 1, 0, "MyMessage");
qmlRegisterType<MessageAuthor>(); //声明方式一
qmlRegisterUncreatableType<MessageAuthor>("MyMessage", 1, 0, "MessageAuthor", "can't create"); //声明方式二

//file: main.qml
import QtQuick 2.13
import MyMessage 1.0

Item {
    width:100; heigth: 100;
    visible: true;
    
    MyMessage {
        author.name: "testName"
        author.email: "testName@qq.com"    
    }
}

5、通过定时器等方式定时动态更新

官方介绍中有两种方式
1 property value write interceptors 即通过 (Behavior 私有类)interceptor 去修改属性值(此方法只能用于 Behavior 类)
2 property value sources 适用于 animation 类

这里只讲解一下第二种方式
条件一:必须是 QObject 和 QQmlPropertyValueSource 子类
条件二:语法必须是 on
条件三:实现 setTarget() 函数

#include <QObject>
#include <QTimer>

class MyMessage : public QObject, public QQmlPropertyValueSource
{
    Q_QOBJECT
    Q_INTERFACES(QQmlPropertyValueSource)
    Q_PROPERTY(int maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
    
public:
    explicit MyMessage(QObject *parent = nullptr) : QObject(parent) , m_maxValue(300){
        connect(&m_timer, &QTimer::timeout, this, &MyMessage::updateProperty);
        m_timer.start(500);
    }
    
    int maxValue() const { return m_maxValue;}
    void setMaxValue( const int &maxValue) {
        if (maxValue == m_maxValue)
            return;
        m_maxValue = maxValue;
        emit maxValueChanged();
    }
    
    virtual void setTarget(const QQmlProperty &prop) {
        if (prop == m_targetProperty)
            return;
        m_targetProperty = prop
    };
    
signals:
    void maxValueChanged();
private slots:
    void updateProperty() {
        m_targetProperty.write(m_maxValue ++);
    }
    
private:
    int m_maxValue;
   QTimer m_timer;
   QQmlProperty m_targetProperty;
};

//file: main.cpp
qmlRegisterType<MyMessage>("MyMessage", 1, 0, "MyMessage");

//file: main.qml
import QtQuick 2.13
import MyMessage 1.0

Item {
    visible: true
    width: 100; height: 100;
    
    Rectangle {
        height: 40
        MyMessage on width { maxValue: 30}
        color: "blue"
    }
}

6、C++ 部分功能延迟完成(所有属性值设定完成之后再进行部分初始化)
此方法仅 QML 环境下可用

条件一:QObject 和 QQmlParserStatus 子类
条件二:实现 componentComplete() 和 classBegin() 具体说明看文档
进行时序:先进行 MyMessage() { } 里的内容,再进行 classBegin() {} 里的内容,最后执行 componentComplete(){} 内容

#include <QObject>
#include <QQmlParserStatus>
#include <QDebug>

class MyMessage : public QObject, public QQmlParserStatus
{
    Q_QOBJECT
    Q_INTERFACES(QQmlParserStatus)
    
public:
    MyMessage() { qDebug() << "I am message";}
    
    void componentComplete() { qDebug() << " I am component";}
    void classBegin() {qDebug() << " I am classBegin" ;}

};

7、QML 版本控制

使用 Q_REVISION()

//伪代码
//file: MyMessage.h
Q_PROPERTY(QString name READ name WRITE setName REVISION 1)

Q_REVISION(1) void setName();

//file:main.cpp
qmlRegister<MyMessage, 1>("MyMessage", 1, 1, "MyMessage");

//file:main.qml
import MyMessage 1.1 


8、副属性 attached objects

简而言之,就是能够让 QML 调用在某个 C++ 实例化的子类,与上面几种情况类似
条件一: QObject 子类
条件二:子类有在宏系统声明的函数
条件三:实现 static *qmlAttachedProperies(QObject *object)
条件四:QML_DECLARE_TYPEINFO() 声明 Flag: QML_HAS_ATTACHED_PROPERTIES

伪代码
file:parent.h 假设子类是 MessageAuthor (直接参考上文出现过的 MessageAuthor.h)
#include <QtQml>
class MyMessage : public QObject
{
    Q_OBJECT
public:
    MyMessage(QObject *parent = nullptr) : QObject(parent){}
    
    static MessageAuthor *qmlAttachedProperties(QObject *object) {
        return new MessageAuthor(object);
    }
};
QML_DECLARE_TYPEINFO(MyMessage, QML_HAS_ATTACHED_PROPERTIES)

file:main.cpp
qmlRegisterType<MyMessage>("MyMessage", 1, 0, "MyMessage");
qmlRegisterType<MessageAuthor>(); //声明一下

file:main.cpp
import QtQuick 2.13
import MyMessage 1.0
Item {
    Rectangle {
        MyMessage.name: "ddd"
        MyMessage.email: "ddd@qq.com"
    }
}

总结:

以上便是关于如何创建在 QML 中调用 C++ 类的方法

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QtQML是两种不同的编程语言,用于开发跨平台的应用程序。Qt是一个完整的框架,用于开发桌面、移动和嵌入式应用程序。QML是一种声明性的语言,用于设计用户界面。 QtQML之间的数据交互可以通过connections和信号机制实现。connections是Qt中用于连接信号函数的宏,可以让两个对象之间建立通信通道。在QML中,可以使用QtQuick里的Connections元素来建立与Qt对象的连接。 在Qt中,通过使用QObject派生类中的信号机制来实现对象之间的通信。信号是一种特殊的成员函数,用于通知其他对象发生了特定的事件。是接收信号的函数,它可以响应接收到的信号并执行相应的操作。 QML中的信号可以通过在QML元素上定义信号属性来实现。QML元素可以作为Qt的对象被使用,在Qt代码中可以通过QObject派生类和Q_PROPERTY宏来访问这些元素。通过使用connections来建立Qt对象和QML元素之间的链接,使得它们能够相互发送信号和响应函数。 QtQML之间的数据交互主要有以下几种方式: 1. 在Qt中定义一个QObject派生类,将其作为QML元素的属性,通过connections建立信号的关联,从而实现数据的传递和交互。 2. 在Qt中定义一个Q_PROPERTY,并在QML中使用property绑定来实现数据的双向绑定。 3. 使用信号来处理用户界面上的事件,比如点击按钮、滑动滑块等。 总的来说,QtQML之间的数据交互可以通过connections和信号机制来实现。这使得QtQML两种语言能够方便地在应用程序中进行通信和数据传递,提高了应用程序的灵活性和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值