C++与QML混合编程

一、前言

Qt Quick技术的引入,使得我们能够快速构建流畅的用户界面,具有动画、各种绚丽效果的UI都不在话下。但它不是万能的,也有很多局限性,原来Qt的一些技术,比如低阶的网络编程如QTcpSocket、多线程,又如XML文档处理类库QXmlStreamReader、QXmlStreamWriter,再如文件操作QFile、QTextStream等,在QML中要么不可用,要么用起来不方便,所以,很多时候我们会基于这样的原则来混合使用QML和C++:使用QML构建界面,使用C++实现非界面的业务逻辑和复杂运算。


二、在QML中使用C++类和对象

QML其实是对ECMAScript的扩展,融合了Qt Object系统,它是一种新的解释型语言,QML引擎虽然由Qt C++实现,但QML对象的运行环境,说到底和C++对象的上下文环境是不同的,是平行的两个世界。

如果你想在QML中访问C++对象,那么必然要找到一种途径在两个运行环境之间建立沟通桥梁。Qt提供了两种在QML环境中使用C++对象的方式:

  • 在C++中实现一个类,注册为QML环境的一个类型,在QML环境中使用该类型创建对象;(导出C++类到QML)
    • 实现C++类;
    • 注册QML类型;
    • 在QML中导入类型;
    • 在QML中创建由C++导出的类型的实例并使用
  • 在C++中构造一个对象,将这个对象设置为QML的上下文属性,在QML环境中直接使用该属性;(导出C++对象到QML)

2.1、导出C++类到QML

2.1.1、定义可以导出的C++类

前提条件
要想将一个类或对象导出到QML中,下列的前提条件必须满足:

  • 从QObject或QObject的派生类继承;
  • 使用Q_OBJECT宏;

看起来好像和使用信号与槽的前提条件一样,没错,的确是一样的。这两个条件是为了让一个类能够进入Qt强大的元对象系统,只有使用元对象系统,一个类的某些方法或属性才可能通过字符串形式的名字来调用,才具有了在QML中访问的基础条件。

一旦你导出了一个类,在QML中就必然要访问该类的实例的属性或方法来达到某种目的,而具有什么特征的属性或方法才可以被QML访问呢?

(1)信号、槽
只要是信号或者槽,都可以在QML中访问,你可以吧C++对象的信号连接到QML中定义的方法上,也可以把QML对象的信号连接到C++对象的槽上,还可以直接调用C++对象的槽或信号。

///ColorMaker类

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>

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

signals:
    void sig_colorChanged(const QColor& color);
    void sig_currentTime(const QString& strTime);

public slots:
    void slot_start();
    void slot_stop();
};

#endif // COLORMAKER_H

定义的两个槽slot_start()、slot_stop()和定义的两个信号sig_colorChanged()、sig_currentTime()都可以在QML中使用;

(2)Q_ENUMS宏
如果你要导出的类定义了想在QML使用的枚举类型,可以使用Q_ENUMS宏将该枚举类型注册到元对象系统中

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>

class ColorMaker : public QObject
{
    Q_OBJECT
    Q_ENUMS(GenerateAlgorithm)

public:
    explicit ColorMaker(QObject *parent = nullptr);
    ~ColorMaker();

    enum GenerateAlgorithm {
        RandomRGB,
        RandomRed,
        RandomGreen,
        RandomBlue,
        LinearIncrease
    };

signals:
    void sig_colorChanged(const QColor& color);
    void sig_currentTime(const QString& strTime);

public slots:
    void slot_start();
    void slot_stop();

};

#endif // COLORMAKER_H

ColorMaker类中添加了GenerateAlgorithm枚举类型 ;
一旦使用了Q_ENUMS宏注册了枚举类型,在QML中就可以用${CLASS_NAME}.${ENUM_VALUE}的形式来访问了,例如:ColorMaker.LinearIncrease

(3)Q_INVOKABLE宏
在定义一个类的成员函数时使用Q_INVOKABLE宏来修饰,就可以让该方法被元对象系统调用。
这个宏必须放在返回类型前面。

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>

class ColorMaker : public QObject
{
    Q_OBJECT
    Q_ENUMS(GenerateAlgorithm)

public:
    explicit ColorMaker(QObject *parent = nullptr);
    ~ColorMaker();

    enum GenerateAlgorithm {
        RandomRGB,
        RandomRed,
        RandomGreen,
        RandomBlue,
        LinearIncrease
    };

    Q_INVOKABLE GenerateAlgorithm algorithm() const;
    Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);

signals:
    void sig_colorChanged(const QColor& color);
    void sig_currentTime(const QString& strTime);

public slots:
    void slot_start();
    void slot_stop();

};

#endif // COLORMAKER_H

一旦使用了Q_INVOKABLE宏将某个方法注册到元对象系统中,在QML中就可以用${Object}.${method}来访问了,例如:

Component.onCompleted: {
	colorMaker.setAlgorithm(Colormaker.LinearIncrease);
}

(4)Q_PROPERTY宏
Q_PROPERTY宏用来定义可通过元对象系统访问的属性,通过它定义的属性,可以在QML中访问、修改,也可以在属性变化时发射特定的信号。

要想使用Q_PROPERTY:

  • 类必须是QObject的后裔;
  • 必须在类首使用Q_OBJECT宏;

关于Q_PROPERTY宏的解释请参考:学习Qt之属性系统详解

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>
#include <QColor>

class ColorMaker : public QObject
{
    Q_OBJECT
    Q_ENUMS(GenerateAlgorithm)

    Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY sig_colorChanged);

public:
    explicit ColorMaker(QObject *parent = nullptr);
    ~ColorMaker();

    enum GenerateAlgorithm {
        RandomRGB,
        RandomRed,
        RandomGreen,
        RandomBlue,
        LinearIncrease
    };

    QColor getColor() const;
    void setColor(const QColor& color);

    Q_INVOKABLE GenerateAlgorithm algorithm() const;
    Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);

signals:
    void sig_colorChanged(const QColor& color);
    void sig_currentTime(const QString& strTime);

public slots:
    void slot_start();
    void slot_stop();

private:
    GenerateAlgorithm m_algorithm;
    QColor m_currentColor;
};

#endif // COLORMAKER_H

至此,C++类就定义完成了,下面是C++类的完整代码:

ColorMaker类完整代码

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>
#include <QColor>

class ColorMaker : public QObject
{
    Q_OBJECT
    Q_ENUMS(GenerateAlgorithm)

    Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY sig_colorChanged);
    Q_PROPERTY(QColor timeColor READ timeColor);

public:
    explicit ColorMaker(QObject *parent = nullptr);
    ~ColorMaker();

    enum GenerateAlgorithm {
        RandomRGB,
        RandomRed,
        RandomGreen,
        RandomBlue,
        LinearIncrease
    };

    QColor getColor() const;
    void setColor(const QColor& color);
    QColor timeColor() const;

    Q_INVOKABLE GenerateAlgorithm algorithm() const;
    Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);

signals:
    void sig_colorChanged(const QColor& color);
    void sig_currentTime(const QString& strTime);

public slots:
    void slot_start();
    void slot_stop();
    
protected:
    void timerEvent(QTimerEvent *event) override;

private:
    GenerateAlgorithm m_algorithm;
    QColor m_currentColor;
    int m_nColorTimer;
};

#endif // COLORMAKER_H
#include "colormaker.h"

ColorMaker::ColorMaker(QObject *parent)
    : QObject(parent)
    ,m_algorithm(RandomRGB)
    ,m_currentColor(Qt::black)
{

}
ColorMaker::~ColorMaker()
{

}

QColor ColorMaker::getColor() const
{
    return m_currentColor;
}
void ColorMaker::setColor(const QColor& color)
{
    m_currentColor = color;
    emit sig_colorChanged(m_currentColor);
}
QColor ColorMaker::timeColor() const
{
    QTime time = QTime::currentTime();
    int r = time.hour();
    int g = time.minute()*2;
    int b = time.second()*4;

    return QColor::fromRgb(r,g,b);
}

ColorMaker::GenerateAlgorithm ColorMaker::algorithm() const
{
    return m_algorithm;
}
void ColorMaker::setAlgorithm(GenerateAlgorithm algorithm)
{
    m_algorithm = algorithm;
}

void ColorMaker::slot_start()
{
    if(m_nColorTimer == 0) {
        m_nColorTimer = startTimer(1000);
    }
}
void ColorMaker::slot_stop()
{
    if(m_nColorTimer > 0) {
        killTimer(m_nColorTimer);
        m_nColorTimer = 0;
    }
}

void ColorMaker::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == m_nColorTimer) {
        switch (m_algorithm) {
            case RandomRGB:
                m_currentColor.setRgb(qrand()%255,qrand()%255,qrand()%255);
                break;
            case RandomRed:
                m_currentColor.setRed(qrand()%255);
                break;
            case RandomGreen:
                m_currentColor.setGreen(qrand()%255);
                break;
            case RandomBlue:
                m_currentColor.setBlue(qrand()%255);
                break;
            case LinearIncrease:
                int r = m_currentColor.red() + 10;
                int g = m_currentColor.green() + 10;
                int b = m_currentColor.blue() + 10;
                m_currentColor.setRgb(r%255,g%255,b%255);
                break;
        }
        emit sig_colorChanged(m_currentColor);
        emit sig_currentTime(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
    }else {
        QObject::timerEvent(event);
    }
}

使用一个周期为1000毫秒的定时器来产生颜色,定时器触发时根据算法来构造新的颜色值,发射sig_colorChanged信号和sig_currentTime信号;


2.1.2、注册一个QML可用的类型

准备好了一个可供QML访问的类之后,还需要将C++类型注册为QML类型;
(1)注册QML类型
要注册一个QML类型,有多种方法可用,例如:

  • qmlRegisterSingletonType()来注册一个单例类型;
  • qmlRegisterType()来注册一个非单例类型;
  • qmlRegisiterTypeNotAvailable()来注册一个类型用来占位;
  • qmlRegisterUncreatableType()来注册一个具有附加属性的附加类型

qmlRegisterType()是个模板函数,有两个原型:

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

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

前一个原型一般用来注册一个新类型,而后一个可以为特定的版本注册类型;后面这个牵涉到Qt Quick的类型和版本机制,三言两语不能言尽,在此单说前一个原型的使用;

要使用qmlRegisterType,需要包含QQmlEngineQtQml头文件

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

//typename: 就是实现的C++类的类名
//uri: 指定一个唯一的包名,类似于Java中的那种,一是用来避免名字冲突,二是可以吧多个相关类聚合到一个包中方便引用;
//比如我们常写的“import QtQuick.Control 1.2”,其中的“QtQuick.Controls”就是包名uri
//1.2是版本,是versionMajor和versionMinor的组合;
//qmlName则是QML中可以使用的类名;

注册实例

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

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

    QGuiApplication app(argc, argv);
    qmlRegisterType<ColorMaker>("an.qt.ColorMaker", 1, 0, "ColorMaker");

    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();
}

注册动作一定要放在QML上下文创建之前,否则的话,没有用;

上面的代码将ColorMaker类注册为QML类ColorMaker,主版本为1,次版本为0,起的包名是an.qt.ColorMaker

(2)在QML中导入C++注册的类型
一旦你在C++中注册好了QML类型,就可以在QML文档中引入你注册的包,然后使用注册的类型了。

要引入包,使用import语句,比如要使用我们注册的ColorMaker类,可以在QML文档中加入下面的import语句:

import an.qt.ColorMaker 1.0

(3)在QML中创建C++导入类型的实例
引入包后,你就可以在QML中创建C++导入类型的对象了,与QML内建类型的使用完全一样;

qml运行效果
在这里插入图片描述

qml完整代码

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

import an.qt.ColorMaker 1.0

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        width: 360;
        height: 360;

        Text {
            id: timeLabel
            anchors.left: parent.left;
            anchors.leftMargin: 4;
            anchors.top: parent.top;
            anchors.topMargin: 4;
            font.pixelSize: 26;
        }

        ColorMaker {
            id: colorMaker;
            color: Qt.green;
        }

        Rectangle {
            id: colorRect;
            anchors.centerIn: parent;
            width: 200;
            height: 200;
            color: "blue";
        }

        Button {
            id: start;
            text: "start";
            anchors.left: parent.left;
            anchors.leftMargin: 4;
            anchors.bottom: parent.bottom;
            anchors.bottomMargin: 4;
            onClicked: {
                colorMaker.slot_start();
            }
        }

        Button {
            id: stop;
            text: "stop";
            anchors.left: start.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                colorMaker.slot_stop();
            }
        }

        function changeAlgorithm(button, algorithm) {
            switch(algorithm) {
                case 0:
                    button.text = "RandomRGB";
                    break;
                case 1:
                    button.text = "RandomRed";
                    break;
                case 2:
                    button.text = "RandomGreen";
                    break;
                case 3:
                    button.text = "RandomBlue";
                    break;
                case 4:
                    button.text = "LinearIncrease";
                    break;
            }
        }

        Button {
            id: colorAlgorithm;
            text: "RandomRGB";
            anchors.left: stop.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                var algorithm = (colorMaker.algorithm() + 1) % 5;
                changeAlgorithm(colorAlgorithm,algorithm);
                colorMaker.setAlgorithm(algorithm);
            }
        }

        Button {
            id: quit;
            text: "quit";
            anchors.left: colorAlgorithm.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                Qt.quit();
            }
        }

        Component.onCompleted: {
            colorMaker.color = Qt.rgba(0,180,120,255);
            colorMaker.setAlgorithm(colorMaker.LinearIncrease);
            changeAlgorithm(colorAlgorithm,colorMaker.algorithm());
        }

        Connections {
            target: colorMaker;
            onSig_currentTime: {
                timeLabel.text = strTime;
                timeLabel.color = colorMaker.timeColor;
            }
        }

        Connections {
            target: colorMaker;
            onSig_colorChanged: {
                colorRect.color = color;
            }
        }
        
    }

}

2.2、导出C++对象到QML

上面讲了怎么把C++类导出为QML可以使用的类型,你还可以把C++中创建的对象作为属性传递到QML环境中,然后再QML环境中访问;
(1)注册属性
要将一个对象注册为属性很简单,例如:

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

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

    QGuiApplication app(argc, argv);
    //qmlRegisterType<ColorMaker>("an.qt.ColorMaker", 1, 0, "ColorMaker");

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("colorMaker", new ColorMaker);
    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();
}
  • engine.rootContext()返回的是QQmlContext对象,QQmlContext类代表一个QML上下文,它的setContextProperty()方法可以为该上下文设置一个全局可见的属性;
  • 注意:new出来的对象,QQmlContext只是使用,不会帮你删除,你需要自己找一个合适的时机来删除它;

(2)在QML中使用关联到C++对象的属性
一旦调用setContextProperty()导出了属性,就可以在QML中使用了,不需要import语句

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

//import an.qt.ColorMaker 1.0

Window {
    id: root;
    width: 400;
    height: 400;
    visible: true;

    Rectangle {
        width: 360;
        height: 360;

        Text {
            id: timeLabel
            anchors.left: parent.left;
            anchors.leftMargin: 4;
            anchors.top: parent.top;
            anchors.topMargin: 4;
            font.pixelSize: 26;
        }

        /*
        ColorMaker {
            id: colorMaker;
            color: Qt.green;
        }
        */

        Rectangle {
            id: colorRect;
            anchors.centerIn: parent;
            width: 200;
            height: 200;
            color: "blue";
        }

        Button {
            id: start;
            text: "start";
            anchors.left: parent.left;
            anchors.leftMargin: 4;
            anchors.bottom: parent.bottom;
            anchors.bottomMargin: 4;
            onClicked: {
                colorMaker.slot_start();
            }
        }

        Button {
            id: stop;
            text: "stop";
            anchors.left: start.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                colorMaker.slot_stop();
            }
        }

        function changeAlgorithm(button, algorithm) {
            switch(algorithm) {
                case 0:
                    button.text = "RandomRGB";
                    break;
                case 1:
                    button.text = "RandomRed";
                    break;
                case 2:
                    button.text = "RandomGreen";
                    break;
                case 3:
                    button.text = "RandomBlue";
                    break;
                case 4:
                    button.text = "LinearIncrease";
                    break;
            }
        }

        Button {
            id: colorAlgorithm;
            text: "RandomRGB";
            anchors.left: stop.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                var algorithm = (colorMaker.algorithm() + 1) % 5;
                changeAlgorithm(colorAlgorithm,algorithm);
                colorMaker.setAlgorithm(algorithm);
            }
        }

        Button {
            id: quit;
            text: "quit";
            anchors.left: colorAlgorithm.right;
            anchors.leftMargin: 4;
            anchors.bottom: start.bottom;
            onClicked: {
                Qt.quit();
            }
        }

        Component.onCompleted: {
            colorMaker.color = Qt.rgba(0,180,120,255);
            //colorMaker.setAlgorithm(colorMaker.LinearIncrease);
            changeAlgorithm(colorAlgorithm,colorMaker.algorithm());
        }

        Connections {
            target: colorMaker;
            onSig_currentTime: {
                timeLabel.text = strTime;
                timeLabel.color = colorMaker.timeColor;
            }
        }

        Connections {
            target: colorMaker;
            onSig_colorChanged: {
                colorRect.color = color;
            }
        }

    }

}

  • main.qml代码主要修改了三处(注释了三处),因为我们导出的属性命名是colorMaker,和导出ColorMaker类是构造的实例的id一样,所以改动不多;
  • 导出的属性可以直接使用,与属性关联的对象。它的信号、槽、可调用的方法(使用Q_INVOKABLE宏修饰的方法)、属性都可以使用;
  • 不能通过类名来引用枚举值,需要使用对象(如colorMaker.LinearIncrease)或者数字字面量;

三、在C++中使用QML对象

我们可以使用QML对象的信号、槽,访问它们的属性,都没有问题,因为很多QML对象对应的类型,原本就是C++类型,比如Image对应QQuickImage,Text对应QQuickText等等;但是,这些与QML类型对应的C++类型都是私有的,你写的C++代码也不能直接访问,怎么办?

Qt最核心的一个基础特性,就是元对象系统,通过元对象系统,你可以查询QObject的某个派生类的类名、有哪些信号、槽、属性、可调用的方法等信息,然后也可以使用QMetaObject::invokeMethod()调用QObject的某个注册到元对象系统中的方法。

而对于使用Q_PROPERTY定义的属性,可以使用QObject的property()方法访问属性,如果该属性定义了WRITE方法,还可以使用setProperty()修改属性。

所以,只要我们找到QML环境中的某个对象,就可以通过元对象系统来访问它的属性、信号、槽等。

3.1、查找一个对象的孩子

QObject类的构造函数有一个parent参数,可以指定一个对象的父亲,QML中的对象其实借助这个组成了以根Item为根的一棵对象树。

而QObject定义了一个属性objectName,这个对象名字属性,就可以用于查找对象。查找对象的方法:findChild()和findChildren():

T QObject::findChild(const QString& name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;

QList<T> QObject::findChildren(const QString& name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;

QList<T> QObject::findChildren(const QRegExp& regExp, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;

QList<T> QObject::findChildren(const QRegularExpression& re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;

它们都是模板方法,从命名上也可以看出来,一个返回单个对象,一个返回对象列表。

我们以Qt Widgets为例来说明,如何查询一个或多个对象;
示例1:查找parentWidget的名为“button1”,类型为QPushButton的孩子

QPushButton* button = parentWidget->findChild<QPushButton*>("button1")

示例2:返回parentWidget的所有名为“widgetname”的QWidget类型的孩子列表

QList<QWidget*> widgets = parentWidget->findChildren<QWidget*>("widgetname")

3.2、使用元对象调用QML对象的方法

QMetaObject的invokeMethod()方法用来调用一个对象的信号、槽、可调用方法。它是个静态方法,原型如下:

bool QMetaObjcet:invokeMethod(
						QObject* obj, 
						const char* member,
						Qt::ConnectionType type,
						QGenericReturnArgument ret,
						QGenericReturnArgument  vla0 = QGenericReturnArgument(0),
						QGenericReturnArgument  vla1 = QGenericReturnArgument(),
						QGenericReturnArgument  vla2 = QGenericReturnArgument(),
						QGenericReturnArgument  vla3 = QGenericReturnArgument(),
						QGenericReturnArgument  vla4 = QGenericReturnArgument(),
						QGenericReturnArgument  vla5 = QGenericReturnArgument(),
						QGenericReturnArgument  vla6 = QGenericReturnArgument(),
						QGenericReturnArgument  vla7 = QGenericReturnArgument(),
						QGenericReturnArgument  vla8 = QGenericReturnArgument(),
						QGenericReturnArgument  vla9 = QGenericReturnArgument());
  • 返回值:返回true说明调用成功;返回false,要么是因为没有你说的那个方法,要么是参数类型不匹配;
  • obj:被调用对象的指针;
  • member:方法名字;
  • type:连接类型;invokeMethod为信号槽而生,你可以指定连接类型,如果被调用的对象和发起调用的线程是同一线程,那么可以使用Qt::DirectConnection、Qt::AutoConnection、Qt::QueuedConnection,如果被调用对象在另一个线程,那么建议使用Qt::QueuedConnection;
  • ret:接收返回值;
  • 然后就是多达10个可以传递给被调用方法的参数;(看来信号槽的参数个数是有限制的,最好不要超过10个)

对于要传递给被调用方法的参数,使用QGenericReturnArgument 来表示,你可以使用Q_ARG宏来构造一个参数,它的定义是:

QGenericReturnArgument Q_ARG(Type, const Type& value);

返回类型是类似的,使用QGenericReturnArgument 表示,你可以使用Q_RETURN_ARG宏来构造一个接收返回值的参数,它的定义是:

QGenericReturnArgument Q_RETURN_ARG(Type, Type& value);

使用:假设一个对象有这么一个槽QString compute(QString, int, double),那么可以这样调用(同步方式):

QString retVal;
QMetaObject::invokeMethod(
							obj, 
							"compute", 
							Qt::DirectConnection,
							Q_RETURN_ARG(QString, retVal),
							Q_ARG(QString, "sqrt"),
							Q_ARG(int, 42),
							Q_ARG(double, 9.7)
						 );

如果你要让一个线程对象退出,可以这么调用(队列连接方式):

QMetaMethod::invokeMethod(thread, "quit", Qt::QueuedConnection);

3.3、callQml示例

运行效果请添加图片描述


示例包含:C++类ChangeColor、main.qml、main.cpp


C++类ChangeColor

#ifndef CHANGECOLOR_H
#define CHANGECOLOR_H

#include <QObject>
#include <QTimer>

class ChangeColor : public QObject
{
    Q_OBJECT
public:
    explicit ChangeColor(QObject* target,QObject *parent = nullptr);
    ~ChangeColor();

protected slots:
    void onTimeout();

private:
    QTimer m_timer;
    QObject* m_target;

};

#endif // CHANGECOLOR_H
#include "changeColor.h"
#include <QDateTime>
#include <QColor>
#include <QVariant>

ChangeColor::ChangeColor(QObject* target,QObject *parent)
    : QObject(parent)
    , m_timer(this)
    , m_target(target)
{
    qsrand(QDateTime::currentDateTime().toTime_t());
    connect(&m_timer,SIGNAL(timeout()),this,SLOT(onTimeout()));
    m_timer.start(1000);
}
ChangeColor::~ChangeColor()
{

}

void ChangeColor::onTimeout()
{
    QColor color = QColor::fromRgb(qrand()%256,qrand()%256,qrand()%256);
    m_target->setProperty("color",color);
}
  • 这个类的作用就是定时器,每隔1秒改变m_target的颜色;
  • 通过构造函数,传递target到m_target,使用m_target->setProperty("color",color);改变目标对象的颜色;

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0

Window {
    objectName: "rootObject";
    visible: true;
    width: 640;
    height: 480;
    title: qsTr("callQml")
    Text {
        objectName: "textLabel";
        text: qsTr("Hello World");
        anchors.centerIn: parent;
        font.pixelSize: 26;
    }

    Button {
        objectName: "quitButton";
        anchors.right: parent.right;
        anchors.rightMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        text: "quit";
    }

}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "changeColor.h"
#include <QMetaObject>
#include <QDebug>
#include <QColor>
#include <QVariant>

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

    QGuiApplication app(argc, argv);

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

    //================================================================
    QObject* root = NULL;
    QList<QObject*> rootObjects = engine.rootObjects();
    int count = rootObjects.size();
    for(int i=0; i<count; ++i) {
        if(rootObjects.at(i)->objectName() == "rootObject") {
            root = rootObjects.at(i);
            break;
        }
    }

    new ChangeColor(root);

    QObject* quitButton = root->findChild<QObject*>("quitButton");
    if(quitButton) {
        QObject::connect(quitButton,SIGNAL(clicked()),&app,SLOT(quit()));
    }

    QObject* textLabel = root->findChild<QObject*>("textLabel");
    if(textLabel) {
        bool bRet = QMetaObject::invokeMethod(textLabel,"setText",Q_ARG(QString,"World Hello"));
        qDebug()<<"call setText return - " << bRet; //1.failed call
        
        textLabel->setProperty("color", QColor::fromRgb(255,0,0));
        
        bRet = QMetaObject::invokeMethod(textLabel,"doLayout");
        qDebug()<<"call doLayout return - " << bRet;
    }
    //================================================================

    return app.exec();
}
  • 有关QML文档根对象的获取,如果使用QQuickView+Item的程序结构方式,通过QQuickView的rootObject()直接就可以得到QML文档的根Item对象;
  • 如果采用QQmlApplicationEngine+Window的程序结构方式,QML文档根对象的获取就麻烦些;示例中通过QQmlApplicationEngine的rootObjects()方法获取到engine加载QML后生成的所有顶层对象的列表,然后遍历、比较objectName,根据QML中设置的对象名字“rootObject”来找到正确的QML根对象;
  • 找到root,创建一个ChangeColor对象,并把root设置为它的m_target,该对象内部使用了一个定时器,一秒改变一次传入对象的颜色;
  • 接着使用findChild()找到了quitButton和textLabel,将quitButton的clicked()信号连接到QGuiApplication的quit()槽上;
  • 在textLabel上,首先我们企图使用invokeMethod()调用setText()方法来改变textLabel的文本,这个注定是会失败的,因为QML中的Text对象对应C++中的是QQuickText类,而QQuickText类没有名为“setText”的槽或者可调用方法;
  • 使用setProperty()修改了textLabel的color属性,所以Hello World这行字变成了红色;
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝勒里恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值