看过了如何在 QML 中使用 C++ 类型或对象,现在来看如何在 C++ 中使用 QML 对象。

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

Qt 最核心的一个基础特性,就是元对象系统,通过元对象系统,你可以查询 QObject 的某个派生类的类名、有哪些信号、槽、属性、可调用方法等等信息,然后也可以使用 QMetaObject::invokeMethod() 调用 QObject 的某个注册到元对象系统中的方法。而对于使用 Q_PROPERTY 定义的属性,可以使用 QObject 的 property() 方法访问属性,如果该属性定义了 WRITE 方法,还可以使用 setProperty() 修改属性。所以只要我们找到 QML 环境中的某个对象,就可以通过元对象系统来访问它的属性、信号、槽等。

一、查找一个对象的孩子

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 :

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

查找 parentWidget 的名为 "button1" 的类型为 QPushButton 的孩子。

示例 2 :

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

返回 parentWidget 所有名为 "widgetname" 的 QWidget 类型的孩子列表。

二、使用元对象调用一个对象的方法

QMetaObject 的 invokeMethod() 方法用来调用一个对象的信号、槽、可调用方法。它是个静态方法,其函数原型如下(省略了部分参数):

bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, ...) [static]
  • 第一个参数是被调用对象的指针。
  • 第二个参数是方法名字。
  • 第三个参数是连接类型,看到这里你就知道, invokeMethod 为信号与槽而生,你可以指定连接类型,如果你要调用的对象和发起调用的线程是同一个线程,那么可以使用 Qt::DirectConnection 或 Qt::AutoConnection 或 Qt::QueuedConnection ,如果被调用对象在另一个线程,那么建议你使用 Qt::QueuedConnection 。
  • 第四个参数用来接收返回指。
  • 然后就是多达 10 个可以传递给被调用方法的参数(这里省略了)。嗯,看来信号与槽的参数个数是有限制的,不能超过 10 个。

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

QGenericArgument Q_ARG( Type, const Type & value)

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

QGenericReturnArgument Q_RETURN_ARG( Type, Type & value)

好啦,总算把这个天杀的函数介绍完了,下面我们看看怎么用。

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

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

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

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

三、callQml 示例

现在让我们创建一个新的项目,名字是 callQml ,添加 changeColor.h 、 changeColor.cpp 两个文件。 main.qml 内容如下:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Window 2.1 Window {
objectName: "rootObject";
width: 360;
height: 360;
visible: true;
Text {
objectName: "textLabel";
text: "Hello World";
anchors.centerIn: parent;
font.pixelSize: 26;
} Button {
anchors.right: parent.right;
anchors.rightMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
text: "quit";
objectName: "quitButton";
}
}

我们给根元素起了个名字 "rootRect" ,给退出按钮起了个名字 "quitButton" ,给文本起了名字 "textLabel" ,我们会在 C++ 代码中通过这些个名字来查找对应的对象并改变它们。

现在来看看 main.cpp :

#include <QtGui/QGuiApplication>
#include <QQmlApplicationEngine>
#include "changeColor.h"
#include <QMetaObject>
#include <QDebug>
#include <QColor>
#include <QVariant> int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv); QQmlApplicationEngine engine;
engine.load(QUrl("qrc:///main.qml")); QObject * root = NULL;//= qobject_cast<QObject*>(viewer.rootObject());
QList<QObject*> rootObjects = engine.rootObjects();
int count = rootObjects.size();
qDebug() << "rootObjects- " << count;
for(int i = 0; i < count; i++)
{
if(rootObjects.at(i)->objectName() == "rootObject")
{
root = rootObjects.at(i);
break;
}
}
new ChangeQmlColor(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)
{
//1. failed call
bool bRet = QMetaObject::invokeMethod(textLabel, "setText", Q_ARG(QString, "world hello"));
qDebug() << "call setText return - " << bRet;
textLabel->setProperty("color", QColor::fromRgb(255,0,0));
bRet = QMetaObject::invokeMethod(textLabel, "doLayout");
qDebug() << "call doLayout return - " << bRet;
} return app.exec();
}

在一开始我通过 viewer.rootObject() ,获取到了作为根元素的 Rectangle ,然后把它交给一个 ChangeQmlColor 对象,该对象会内部通过一个定时器,一秒改变一次传入对象的颜色。

紧接着,我使用 QObject 的 findChild() 找到了 quitButton 按钮,把它的 clicked() 信号连接到 QGuiApplication 的 quit() 槽上。所以你点击这个按钮,应用就退出了。

后来,我又通过名字 "textLabel" 找到了 textLabel 对象。首先我企图使用 invodeMethod() 调用 setText() 方法来改变 textLabel 的文本,这个注定是会失败的,因为 QML 中的Text 对象对应 C++ QQuickText 类,而 QQuickText 没有名为 setText 的槽或者可调用方法。我查看了头文件 qquicktext_p.h ,发现它有一个使用 Q_INVOKABLE 宏修饰的 doLayout() 的方法,所以后来我又调用 doLayout() ,这次成功了。

下图是运行效果:

Hello World 这行字变成了红色,是因为我在 main() 函数中使用 setProperty 修改了 textLabel 的 color 属性。

下面是 Qt Creator 应用程序输出窗口的信息,可以验证对 Text 方法的调用是否成功:

Starting D:\projects\...\release\callQml.exe...
QMetaObject::invokeMethod: No such method QQuickText::setText(QString)
call setText return - false
call doLayout return - true

好啦,最后看看界面背景为么变成了浅绿色。这正是下面这行代码的功劳:

new ChangeQmlColor(rootItem);

它以 rootItem 为参数创建了一个 ChangeQmlColor 对象,而 ChangeQmlColor 类会改变传给它的对象的颜色。

ChangeQmlColor 类定义如下:

#ifndef CHANGECOLOR_H
#define CHANGECOLOR_H
#include <QObject>
#include <QTimer> class ChangeQmlColor : public QObject
{
Q_OBJECT
public:
ChangeQmlColor(QObject *target, QObject *parent = 0);
~ChangeQmlColor(); protected slots:
void onTimeout(); private:
QTimer m_timer;
QObject *m_target;
}; #endif

实现文件 changeColor.cpp :

#include "changeColor.h"
#include <QDateTime>
#include <QColor>
#include <QVariant> ChangeQmlColor::ChangeQmlColor(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);
} ChangeQmlColor::~ChangeQmlColor()
{} void ChangeQmlColor::onTimeout()
{
QColor color = QColor::fromRgb(qrand()%256, qrand()%256, qrand()%256);
m_target->setProperty("color", color);
}

参考:

《Qt Quick核心编程》第11章

Qt Quick 之 QML 与 C++ 混合编程详解

在 C++ 中使用 QML 对象的更多相关文章

  1. QML Object Attributes QML对象属性

    QML Object Attributes Every QML object type has a defined set of attributes. Each instance of an obj ...

  2. QtQuick 中的 qml 与 Qt 的 C++

    QtQuick 可以使用内置的 JavaScript 引擎加载相应的 JS 代码,使用起来特别方便. 在 Qt 中使用 C++ 开发底层,QtQuick 用来加载.处理图像,然后使用 Qt 提供的接口 ...

  3. Python操作qml对象

    1. 如何在python里获得qml里的对象? 1.1 获取根对象 QML: import QtQuick 2.12 import QtQuick.Controls 2.12 ApplicationW ...

  4. C#开发中使用配置文件对象简化配置的本地保存

    C#开发中使用配置文件对象简化配置的本地保存 0x00 起因 程序的核心是数据和逻辑,开发过程中免不了要对操作的数据进行设置,而有些数据在程序执行过程中被用户或程序做出的修改是应该保存下来的,这样程序 ...

  5. 批量解密SQLSERVER数据库中的各种对象的工具dbForge SQL Decryptor

    批量解密SQLSERVER数据库中的各种对象的工具dbForge SQL Decryptor2.1.11 之前写过一篇文章,使用redgate公司的SQL PROMPT工具,但是不太方便 SQLPRO ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-4  POCO中使用值对象(Complex Type--也叫复合类型)属性 问题 ...

  7. 关于Javascript中通过实例对象修改原型对象属性值的问题

    Javascript中的数据值有两大类:基本类型的数据值和引用类型的数据值. 基本类型的数据值有5种:null.undefined.number.boolean和string. 引用类型的数据值往大的 ...

  8. DOM中的事件对象

    三.事件对象事件对象event1.DOM中的事件对象(1).type:获取事件类型(2).target:事件目标(3).stopPropagation() 阻止事件冒泡(4).preventDefau ...

  9. MongoDB学习笔记~MongoDB实体中的值对象

    回到目录 注意,这里说的值对象是指在MongoDB实体类中的,并不是DDD中的值对象,不过,两者也是联系,就是它是对类的补充,自己本身没有存在的价值,而在值对象中,也是不需要有主键Id的,这与DDD也 ...

随机推荐

  1. sso单点登录的入门(Session跨域、Spring-Session共享)

    1.单点登录,就是多系统,单一位置登录,实现多系统同时登录的一种技术.单点登录一般是用于互相授信的系统,实现单一位置登录,全系统有效的. 区分与三方登录(第三方登录) ,三方登录:某系统,使用其他系统 ...

  2. Asp.Net Mvc日志处理

    /// <summary> /// 日志处理帮助类 /// </summary> public class LogHelper { private static Queue&l ...

  3. c# 值类型和引用类型 笔记

    参考以下博文,我这里只是笔记一下,原文会更加详细 c#基础系列1---深入理解值类型和引用类型 堆栈和托管堆c# 值类型和引用类型 红色表示——“这啥?”(真实1个问题引出3个问题) CLR支持的两种 ...

  4. Java学习——注解

    Java学习——注解 摘要:本文主要介绍了Java开发常用的注解,以及如何自定义注解. 部分内容来自以下博客: https://www.cnblogs.com/Qian123/p/5256084.ht ...

  5. 我脑中的JVM大全附带Java8的特性

    Java虚拟机-sun classic vm 世界上第一款商用的Java虚拟机. 只能使用纯解释器的方式来执行Java代码. Java虚拟机-ExactVM Exact Memory Manageme ...

  6. 读《TCP/IP详解》:TCP

    TCP(Transmission Control Protocol,传输控制协议),位于传输层,提供一种面向连接.可靠的字节流服务. 字节流服务(Byte Stream Service)是指,为了方便 ...

  7. 从html富文本中提取纯文本

    其实从html富文本中提取纯文本很简单,富文本基本上是使用html标签给文本加上丰富多彩的样式. 所以只需要将富文本字符串中的“<.....>”标签剔除,即可得到纯文本.我们可以使用正则表 ...

  8. bert,albert的快速训练和预测

    随着预训练模型越来越成熟,预训练模型也会更多的在业务中使用,本文提供了bert和albert的快速训练和部署,实际上目前的预训练模型在用起来时都大致相同. 基于不久前发布的中文数据集chineseGL ...

  9. 201871010104-陈园园 《面向对象程序设计(java)》第六——七周学习总结

    201871010104-陈园园 <面向对象程序设计(java)>第六——七周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daiz ...

  10. Pytorch 使用不同版本的 cuda

    由于课题的原因,笔者主要通过 Pytorch 框架进行深度学习相关的学习和实验.在运行和学习网络上的 Pytorch 应用代码的过程中,不少项目会标注作者在运行和实验时所使用的 Pytorch 和 c ...