QT QML HTML通讯

使用 Qt WebChannel 实现 C++/QML 和 HTML 页面之间交互

在项目开发中,经常会有在原生应用程序中嵌入 HTML 页面或者 Web 项目,而且须要应用程序与所加载的 HTML 页面的相互通讯的需求。javascript

本篇文章基于 Qt 框架,讲解如何使用 Qt WebChannel 实现 C++/QML 和 HTML 页面之间交互,包括:php

  • 在 HTML 页面调用 C++/QML 对象中的函数(异步)
  • 向 HTML 中发送 QML/C++ 信号
  • 在 HTML 中调用 QML/C++ 对象的属性
  • 在 HTML 中调用 QML/C++ 对象的枚举类型和枚举值

2011 年 10 月,诺姆·罗森塔尔(Noam Rosenthal)提出了这个方法,他称之为 Qt WebChannel。他的想法既简单又强大:经过利用 Qt 的自省(introspection,也叫内省)系统,他能够在 JavaScript 客户端建立模拟对象,该对象能够反射服务端 QML/QObject 对象的 API。对于 QML 主机和 HTML/JavaScript 客户端之间的通讯方式,他选择了 WebSockets,可是 WebChannel 提供的 API 是彻底异步的。html

Qt WebChannel 支持服务端(QML/C++ 应用程序)和客户端(HTML/JavaScript 或 QML 应用程序)之间的点对点(peer-to-peer)通讯。它提供了一个 JavaScript 库,用于将 C++ 和 QML 应用程序与 HTML/JavaScript 和 QML 客户端无缝集成。客户端必须使用 JavaScript 库来访问主机端应用程序发布的序列化的 QObjects 对象。java

注:本文中提到的客户端和服务端实际上是在同一个应用程序内,由于 WebChannel 是基于 WebSocket 实现的,因此会有这种客户端和服务端的叫法。c++

QML/HTML 混合应用程序

本节演示 QML 应用程序和 HTML 之间如何进行交互。git

本应用用到了 WebChannel 和 WebEngine 两个主要模块,因此要将下面这行添加到 qmake .pro 文件中:github

QT += webchannel webengine

QML 服务端

在 QML 服务端,首先导入 Qt WebChannel 模块,以及 Qt WebEngine 模块:web

import QtWebChannel 1.0
import QtWebEngine 1.5

而后建立一个想要发布到 HTML/JavaScript 客户端的对象:算法

QtObject {
    id: myObject

    // 注册方法 1
    // 使用注册方法 2 时不须要此行代码
    WebChannel.id: "foo" //这个 id 能够在 html 中使用
    
    // 如下为 JavaScript 代码能够访问的信号、方法和属性
    signal someSignal(string message);

    function someMethod(message) {
        console.log(message);
        someSignal(message);
        return "foobar";
    }

    property string hello: "world"
}

最后将该对象在 WebView 控件中发布到 HTML 客户端中:chrome

WebEngineView {
    anchors.fill: parent
    url: "file:///C:/test.html"
    
    webChannel: WebChannel {
        id: webChannel

        // 注册方法 1
        registeredObjects: [myObject]
        // 注册方法 2
        //Component.onCompleted: {
        //    // "foo" 是该对象在 JavaScript 端的调用标识
        //    webChannel.registerObject("foo", myObject)
        //}
    }
}

HTML / JavaScript 客户端

在客户端,首先,经过 Qt 资源 URL 包含客户端 qwebchannel.js 库(该文件能够在 %QtDir%\Src\qtwebchannel\examples\webchannel\shared\qwebchannel.js 中找到,通过测试在 Qt 5.12 之后不将该文件加入资源也可),并在 HTML 文件中插入一段 JavaScript:

<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>

而后,在 JavaScript 代码中实例化 QWebChannel 对象并设置回调函数。当 web 通道的初始化成功时,将调用回调函数。此外,您能够传递 qt.webChannelTransport 对象到 channel 中,详见下文:

new QWebChannel(qt.webChannelTransport, function(channel) {
    // 全部经过 WebChannel 发布的对象均可以在 channel.objects 中访问的到
    window.foo = channel.objects.foo;
 
    // 访问一个属性
    alert(foo.hello);
    
    // Writing a property will instantly update the client side cache.
    // The remote end will be notified about the change asynchronously
    foo.hello = "Hello World!";
 
    // 链接信号
    foo.someSignal.connect(function(message) {
        alert("Got signal: " + message);
    });
 
    // 调用方法,并*异步*接收返回值
    foo.someMethod("bar", function(ret) {
        alert("Got return value: " + ret);
    });
    
    // One can also access enums that are marked with Q_ENUM:
    //console.log(foo.MyEnum.MyEnumerator);
});

C++/QML/HTML 混合应用程序

利用在 QML 应用程序中能够引用 C++ 类的这个特色,咱们也能够实如今 HTML 页面中调用 C++ 类的需求。

首先定义 C++ 对象并将它注册到 QML 中,咱们在 main.cpp 中添加下面的一行,注意, TestObejct 类必须直接继承自 Object:

qmlRegisterType<TestObejct>("TestObejct", 1, 0, "TestObejct");

而后在 QML 文件中将这个类对象注册到 WebChannel,你可使用上节中提到的方法直接调用 C++ 类中已存在的信号、方法、属性和枚举类型,也能够在 QML 中继续扩展其余方法:

import TestObejct 1.0
...
TestObejct {
    id: myObject
    WebChannel.id: "foo"
    
    // 在 QML中能够继续扩展信号、方法和属性
    signal someSignal2(string message);

    function someMethod2(message) {
        console.log(message);
        someSignal2(message);
        return "foobar";
    }

    property string hello2: "world"
}

Qt WebChannel 不仅能够在 QML 应用程序中使用,在纯 Qt/C++ 应用程序中也能够建立一个 QWebChannel 并发布 QObject 实例。

 参考:https://www.pressc.cn/1085.html

---------------------------------------------------------------------------------------------------------------------------------------------------

 

写在前面

本文适合有必定Qt及HTML经验的人阅读。

Qt(C++)和QML间交互

想要了解Qt(C++)和QML间的信息交互,就不得不提到Qt的信号与槽机制。

信号与槽

信号与槽是qt的特有信息传输机制。它本质上是一种观察者模式。当某个事件触发时,它就会发出一个相似广播的信号。若是有对象对这个信号感兴趣,它就使用链接函数,将想要处理的信号和本身的一个函数(qt中成为槽)绑定来进行处理。当信号发出时,槽函数就会自动被执行。

咱们经过一个例子来进行说明。

类的定义

首先,咱们定义一个c++的类,该类须要继承QObject类,这样才有信号槽的能力。同时,须要在该类中添加Q_OBJECT宏。例:

#include <QObject>classMyClass : public QObject
{
    Q_OBJECT
};

使用Q_PROPERTY定义属性,该属性可被qml使用。它还具备一些附加特性:READ用于读属性的值;WRITE用于设置属性的值;NOTIFY则定义一个信号,该信号用来表示属性发生改变。信号会携带一个参数,表示属性的新值。l例如:

Q_PROPERTY(QString mystring READ getString WRITE setString NOTIFY mystringChanged)

绑定槽函数

QT使用connect函数来绑定信号和槽,例:

connect(this, SIGNAL(mystringChanged(QString)), this, SLOT(onMystringChanged(QString)));

上面代码中,当有mystringChanged信号发出时,onMystringChanged函数就是被执行。信号与槽机制不只能够用来进行c++类之间的通讯,也能够用来实现c++和qml之间的通讯,咱们继续使用上面的例子来讲明。

信号发送

当mystring有变化时,会触发setString回调。咱们能够在setString函数中触发mystringChanged信号。

void MyClass::setString(QString string){
    emit mystringChanged(string);//发送信号
}

将类注册到QML中

QT使用qmlRegisterType方法将类注册到QML中,例:

qmlRegisterType<MyClass>("RegisterMyType",1,0,"MyClassType");

其中,第一个参数是做为QML中引用的url;第二个参数是主版本号;第三个参数是次版本号;第四个参数是QML中使用的元素名称。本例中,QML模块可使用下面方法引用该类,例:

import RegisterMyType 1.0

QML中对象建立

通过上面的步骤以后,咱们就能够直接在QML中建立MyClassType对象了。例:

MyClassType {
    id: myobj
}

QML中链接信号

对象建立成功后,咱们能够为QML绑定感兴趣的信号了。

Connections {
	target: myobj;
	onMystringChanged: {
		// 这里的value是signal信号函数里面的参数
		console.log("value: " + value)
	}
}

QML直接使用对象

除了上面的方法,咱们还能够经过直接使用对象的方式,来进行信号的绑定。在上面的例子中,咱们能够下面的方式,咱们首先在C++代码中作以下声明:

QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine;
QQmlComponent component(qmlEngine, QUrl("qrc:/MyClass.qml"));
MyClass *myClass = qobject_cast<MyClass *>(component.create());
qmlEngine->rootContext()->setContextProperty("myQmlClass", myClass);

其中,QQmlComponent用来封装QML组件。须要注意的是,MyClass.qml中,须要使用上面讲到的MyClassType做为顶层元素。 setContextProperty函数定义暴露给QML的对象。第一个参数是QML中使用的对象名称,至关于重命名,可在QML中直接使用;第二个参数暴露给QML的对象。而信号的绑定,只须要将上面讲到的Connections中的target修改成myQmlClass便可。即:

Connections {
	target: myQmlClass;
	onMystringChanged: {
		// 这里的value是signal信号函数里面的参数
		console.log("value: " + value)
	}
}

Qt和HTML间交互

Qt和HTML间的交互式经过WebChannel来实现的。

WebChannel

WebChannel提供一种机制使得QObject或者QML访问HTML。全部的属性,信号和公共的槽函数均可以被HTML使用。

WebChannel由一些属性和方法组成。

属性包括:

registeredObjects

A list of objects which should be accessible to remote clients.

The objects must have the attached id property set to an identifier, under which the object is then known on the HTML side.

Once registered, all signals and property changes are automatically propagated to the clients. Public invokable methods, including slots, are also accessible to the clients.

If one needs to register objects which are not available when the component is created, use the imperative registerObjects method.

简单翻译一下:

这是一个提供给HTML访问的object列表。

object须要有id标识,这样才能被HTML识别。

object一旦被注册,全部的信号和属性的改变都会被自动传递到客户端。还包括公共的方法和槽。

若是组件建立时object还不可用,可使用registerObject方法。

transports

A list of transport objects, which implementQWebChannelAbstractTransport. The transports are used to talk to the remote clients.

一个传输列表,实现了QWebChannelAbstractTransport类。用来跟客户端交互。

其它方法具体再也不赘述,能够参考后面的参考文献。这里咱们主要讲一下registeredObjects的用法。

registeredObjects提供给HTML访问的object列表。object须声明id属性,这样HTML才能知道它;同时,object要声明WebChannel.id,HTML使用该id访问对象。

QtObject定义以下:

QtObject {
	id: myObject
	WebChannel.id: "myWebObject"
	property string name: "QtObjectName"
	signal onMystringChanged(var myStr)
}

WebChannel定义以下:

WebChannel {
	id: myChannel
	registeredObjects: [myObject]
}

WebEngineView

WebChannel声明好以后,下面就是如何使用它。咱们定义WebEngineView元素,用来加载HTML文件,并指定关联的WebChannel,使用方式以下:

WebEngineView {
    id: webView
    url: "./map.html"
    webChannel: myChannel
}

至此,QML端的工做就已经完成了。下面讲一下如何在HTML端使用WebChannel。

引入qwebchannel.js库

HTML想要使用QWebChannel,须要先引用qwebchannel库,这是一个JavaScript类库,使用方式以下:

<script type="text/javascript" src="qwebchannel.js"></script>

而后在增长以下代码:

new QWebChannel(qt.webChannelTransport, function(channel) {
	var myClass = channel.objects.myClass;
	var myObject = channel.objects.myWebObject;

	myObject.onMystringChanged.connect(function(myStr) {
		console.log(myStr);
	});
});

总结

信号与槽机制是Qt的核心思想,咱们须要加深理解,在实际应用中灵活使用。

这里只讲了C++和QML,QML和HTML间的交互。C++和HTML间也能够直接交互,之后有时间再来跟你们一块儿分享。

能力有限,若是错误之处,请不吝赐教,谢谢。

  参考:https://zhuanlan.zhihu.com/p/62987738

---------------------------------------------------------------------------------------------------------------------------------------------------

 

 

 

qmlRegisterType 是一个能够将C++实现的类在QML中调用的,链接C++和QML的一个工具,很是重要的函数!!!

首先来看QtHelp关于qmlRegisterType 的介绍

int qmlRegisterType(const char * uri, int versionMajor, int versionMinor, const char * qmlName)
This template function registers the C++ type in the QML system with the name qmlName, in the library imported from uri having the version number composed from versionMajor and versionMinor.
Returns the QML type id.

能够看到qmlRegisterType里总共4个参数,第一个参数* uri指的是QML中import后的内容,至关于头文件名,第二个第三个参数分别是主次版本号,第四个指的是QML中类的名字。
下面举个例子
在main.cpp文件中

#include <QtQml>
qmlRegisterType<MySliderItem>("com.mycompany.qmlcomponents", 1, 0, "Slider");
 

在main.qml文件中:

import com.mycompany.qmlcomponents 1.0
Slider {
}
 

注意:第四个QML的类名首字母必定要大写,要否则会报错。。并且是那种你找不到的。。
有两种使用方法:

 int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

 int qmlRegisterType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName)
 

 

后面一种只是多了一个连接参数,能够链接非本地的各种库函数

  QUrl url("http://www.example.com/List of holidays.xml");
  // url.toEncoded() == "http://www.example.com/List%20of%20holidays.xml"
 
 
---------------------------------------------------------------------------------------------------------------------------------------------------
 
 

接下来咱们的学习将会伴随 colorMaker 项目进行,等咱们讲完,一个完整的 colorMaker 项目也会完成。须要新建两个文件, colorMaker.h 和 colorMaker.cpp 。

    colorMaker 只是一个示例项目,我在 C++ 中实现一个 ColorMaker 类,它能够被注册为一个 QML 类型供 QML 像内建类型同样使用,它的实例也能够导出为 QML 上下文属性在 QML 中访问。咱们的示例只是在界面顶部显示当前时间(时间文字的颜色随时间变化而变化),在界面中间显示一个变色矩形,在界面底部放置几个按钮来控制颜色如何变化。

    图 1 是效果图:

               图 1 colorMaker 效果图

在 QML 中使用 C++ 类和对象
    咱们知道, QML 实际上是对 JavaScript 的扩展,融合了 Qt Object 系统,它是一种新的解释型的语言, QML 引擎虽然由 Qt C++ 实现,但 QML 对象的运行环境,说到底和 C++ 对象的上下文环境是不一样的,是平行的两个世界。若是你想在 QML 中访问 C++ 对象,那么必然要找到一种途径来在两个运行环境之间创建沟通桥梁。

    Qt 提供了两种在 QML 环境中使用 C++ 对象的方式:

在 C++ 中实现一个类,注册到 QML 环境中, QML 环境中使用该类型建立对象
在 C++ 中构造一个对象,将这个对象设置为 QML 的上下文属性,在 QML 环境中直接使用改属性
    无论哪一种方式,对要导出的 C++ 类都有要求,不是一个类的全部方法、变量均可以被 QML 使用,所以咱们先来看看怎样让一个方法或属性能够被 QML 使用。

实现能够导出的 C++ 类
前提条件   
     要想将一个类或对象导出到 QML 中,下列前提条件必须知足:
    从 QObject 或 QObject 的派生类继承
    使用 Q_OBJECT 宏
    看起来好像和使用信号与槽的前提条件同样……没错,的确是同样的。这两个条件是为了让一个类可以进入 Qt 强大的元对象系统(meta-object system)中,只有使用元对象系统,一个类的某些方法或属性才可能经过字符串形式的名字来调用,才具备了在 QML 中访问的基础条件。
    一旦你导出了一个类,在 QML 中就必然要访问该类的实例的属性或方法来达到某种目的,不然我真想不来你要干什么……而具备什么特征的属性或方法才能够被 QML 访问呢?
信号,槽
    只要是信号或者槽,均可以在 QML 中访问,你能够把 C++ 对象的信号链接到 QML 中定义的方法上,也能够把 QML 对象的信号链接到 C++ 对象的槽上,还能够直接调用 C++ 对象的槽或信号……因此,这是最简单好用的一种途径。

  1.  
    classColorMaker :public QObject
  2.  
    {
  3.  
    Q_OBJECT
  4.  
     
  5.  
    public:
  6.  
    ColorMaker(QObject *parent = 0);
  7.  
    ~ColorMaker();
  8.  
     
  9.  
    signals:
  10.  
    voidcolorChanged(const QColor & color);
  11.  
    voidcurrentTime(const QString &strTime);
  12.  
     
  13.  
    public slots:
  14.  
    voidstart();
  15.  
    voidstop();
  16.  
     
  17.  
    };

咱们定义了 start() / stop() 两个槽, colorChanged() / currentTime() 两个信号,均可以在 QML 中使用。
Q_INVOKABLE 宏
    在定义一个类的成员函数时使用 Q_INVOKABLE 宏来修饰,就可让该方法被元对象系统调用。这个宏必须放在返回类型前面。

  1.  
     
  2.  
    classColorMaker :public QObject
  3.  
    {
  4.  
    Q_OBJECT
  5.  
     
  6.  
    public:
  7.  
    ColorMaker(QObject *parent = 0);
  8.  
    ~ColorMaker();
  9.  
     
  10.  
    Q_INVOKABLE GenerateAlgorithm algorithm()const;
  11.  
    Q_INVOKABLE voidsetAlgorithm(GenerateAlgorithm algorithm);
  12.  
     
  13.  
    signals:
  14.  
    voidcolorChanged(const QColor & color);
  15.  
    voidcurrentTime(const QString &strTime);
  16.  
     
  17.  
    public slots:
  18.  
    voidstart();
  19.  
    voidstop();
  20.  
     

 一旦你使用 Q_INVOKABLE 将某个方法注册到元对象系统中,在 QML 中就能够用 ${Object}.${method} 来访问,colorMaker 的 main.qml 中有使用 algorithm() 和 setAlgorithm() 的 QML 代码 :
  

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

Q_ENUMS
    若是你要导出的类定义了想在 QML 中使用枚举类型,可使用 Q_ENUMS 宏将该枚举注册到元对象系统中。
    ColorMaker 类定义了 GenerateAlgorithm 枚举类型,支持 RandomRGB / RandomRed 等颜色生成算法。如今 ColorMaker 类的声明变成了这个样子:
 

  1.  
    classColorMaker :public QObject
  2.  
    {
  3.  
    Q_OBJECT
  4.  
    Q_ENUMS(GenerateAlgorithm)
  5.  
     
  6.  
    public:
  7.  
    ColorMaker(QObject *parent = 0);
  8.  
    ~ColorMaker();
  9.  
     
  10.  
    enum GenerateAlgorithm{
  11.  
    RandomRGB,
  12.  
    RandomRed,
  13.  
    RandomGreen,
  14.  
    RandomBlue,
  15.  
    LinearIncrease
  16.  
    };
  17.  
     
  18.  
    Q_INVOKABLE GenerateAlgorithm algorithm()const;
  19.  
    Q_INVOKABLE voidsetAlgorithm(GenerateAlgorithm algorithm);
  20.  
     
  21.  
    signals:
  22.  
    voidcolorChanged(const QColor & color);
  23.  
    voidcurrentTime(const QString &strTime);
  24.  
     
  25.  
    public slots:
  26.  
    voidstart();
  27.  
    voidstop();
  28.  
    };

 

一旦你使用 Q_ENUMS 宏注册了你的枚举类型,在 QML 中就能够用 ${CLASS_NAME}.${ENUM_VALUE} 的形式来访问,好比 ColorMaker.LinearIncrease ,上节展现的 QML 代码片断已经使用了导出的枚举类型。

 

Q_PROPERTY
    Q_PROPERTY 宏用来定义可经过元对象系统访问的属性,经过它定义的属性,能够在 QML 中访问、修改,也能够在属性变化时发射特定的信号。要想使用 Q_PROPERTY 宏,你的类必须是 QObject 的后裔,必须在类首使用 Q_OBJECT 宏。
    下面是 Q_PROPERTY 宏的原型:
 

  1.  
    Q_PROPERTY(type name
  2.  
    (READ getFunction [WRITE setFunction] |
  3.  
    MEMBER memberName [(READ getFunction | WRITE setFunction)])
  4.  
    [RESET resetFunction]
  5.  
    [NOTIFY notifySignal]
  6.  
    [REVISION int]
  7.  
    [DESIGNABLE bool]
  8.  
    [SCRIPTABLE bool]
  9.  
    [STORED bool]
  10.  
    [USER bool]
  11.  
    [CONSTANT]
  12.  
    [FINAL])

    是否是很复杂?你能够为一个属性命名,能够设定的选项数超过10个……我是以为有点儿头疼。不过,不是全部的选项都必须设定,看一个最简短的属性声明:

Q_PROPERTY(int x READ x)

上面的声明定义了一个类型为 int 名为 x 的属性,经过方法 x() 来访问。
    type name 这两个字段想必不用细说了吧? type 是属性的类型,能够是 int / float / QString / QObject / QColor / QFont 等等, name 就是属性的名字。
    其实咱们在实际使用中,不多可以用全 Q_PROPERTY 的全部选项,就往 QML 导出类这种场景来讲,比较经常使用的是 READ / WRITE / NOTIFY 三个选项。咱们来看看都是什么含义。
READ 标记,若是你没有为属性指定 MEMBER 标记,则 READ 标记必不可少;声明一个读取属性的函数,该函数通常没有参数,返回定义的属性。
WRITE 标记,可选配置。声明一个设定属性的函数。它指定的函数,只能有一个与属性类型匹配的参数,必须返回 void 。
NOTIFY 标记,可选配置。给属性关联一个信号(该信号必须是已经在类中声明过的),当属性的值发生变化时就会触发该信号。信号的参数,通常就是你定义的属性。
 

    其它标记的含义,请参考 Qt SDK 。

    QML 中的 Text 类型对应 C++ 中的 QQuickText 类,下面是我摘取的部分代码,能够看到 Q_ENUMS 和 Q_PROPERTY 的使用:

  1.  
    class QQuickText : public QQuickImplicitSizeItem
  2.  
    {
  3.  
    Q_OBJECT
  4.  
    Q_ENUMS(HAlignment)
  5.  
     
  6.  
     
  7.  
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
  8.  
    Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
  9.  
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
  10.  
    ...
  11.  
     
  12.  
    public:
  13.  
    enum HAlignment { AlignLeft = Qt::AlignLeft,
  14.  
    AlignRight = Qt::AlignRight,
  15.  
    AlignHCenter = Qt::AlignHCenter,
  16.  
    AlignJustify = Qt::AlignJustify };
  17.  
    ...
  18.  
    QString text() const;
  19.  
    void setText(const QString &);
  20.  
     
  21.  
    QFont font() const;
  22.  
    void setFont(const QFont &font);
  23.  
     
  24.  
    QColor color() const;
  25.  
    void setColor(const QColor &c);
  26.  
    ...
  27.  
    };

   如今给咱们的 ColorMaker 类添加一些属性,以便 QML 能够获取、设置颜色值。新的 ColorMaker 类以下:

  1.  
    classColorMaker :public QObject
  2.  
    {
  3.  
    Q_OBJECT
  4.  
    Q_ENUMS(GenerateAlgorithm)
  5.  
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
  6.  
    Q_PROPERTY(QColor timeColor READ timeColor)
  7.  
     
  8.  
    public:
  9.  
    ColorMaker(QObject *parent = 0);
  10.  
    ~ColorMaker();
  11.  
     
  12.  
    enum GenerateAlgorithm{
  13.  
    RandomRGB,
  14.  
    RandomRed,
  15.  
    RandomGreen,
  16.  
    RandomBlue,
  17.  
    LinearIncrease
  18.  
    };
  19.  
     
  20.  
    QColor color()const;
  21.  
    voidsetColor(const QColor & color);
  22.  
    QColor timeColor()const;
  23.  
     
  24.  
    Q_INVOKABLE GenerateAlgorithm algorithm()const;
  25.  
    Q_INVOKABLE voidsetAlgorithm(GenerateAlgorithm algorithm);
  26.  
     
  27.  
    signals:
  28.  
    voidcolorChanged(const QColor & color);
  29.  
    voidcurrentTime(const QString &strTime);
  30.  
     
  31.  
    public slots:
  32.  
    voidstart();
  33.  
    voidstop();
  34.  
     
  35.  
    protected:
  36.  
    voidtimerEvent(QTimerEvent *e);
  37.  
     
  38.  
    private:
  39.  
    GenerateAlgorithm m_algorithm;
  40.  
    QColor m_currentColor;
  41.  
    int m_nColorTimer;
  42.  
    };

   如今咱们的 ColorMaker 已是一个完整的类了,有信号、有槽、有使用 Q_INVOKABLE 注册的方法,还导出了枚举类型,小小麻雀五脏俱全。

    是时候看看它的实现了。翠花,上代码:

  1.  
    #include "colorMaker.h"
  2.  
    #include <QTimerEvent>
  3.  
    #include <QDateTime>
  4.  
     
  5.  
    ColorMaker::ColorMaker(QObject *parent)
  6.  
    : QObject(parent)
  7.  
    , m_algorithm(RandomRGB)
  8.  
    , m_currentColor(Qt::black)
  9.  
    , m_nColorTimer(0)
  10.  
    {
  11.  
    qsrand(QDateTime::currentDateTime().toTime_t());
  12.  
    }
  13.  
     
  14.  
    ColorMaker::~ColorMaker()
  15.  
    {
  16.  
    }
  17.  
     
  18.  
    QColor ColorMaker::color() const
  19.  
    {
  20.  
    return m_currentColor;
  21.  
    }
  22.  
     
  23.  
    void ColorMaker::setColor(const QColor &color)
  24.  
    {
  25.  
    m_currentColor = color;
  26.  
    emit colorChanged(m_currentColor);
  27.  
    }
  28.  
     
  29.  
    QColor ColorMaker::timeColor() const
  30.  
    {
  31.  
    QTime time = QTime::currentTime();
  32.  
    int r = time.hour();
  33.  
    int g = time.minute()*2;
  34.  
    int b = time.second()*4;
  35.  
    return QColor::fromRgb(r, g, b);
  36.  
    }
  37.  
     
  38.  
    ColorMaker::GenerateAlgorithm ColorMaker::algorithm() const
  39.  
    {
  40.  
    return m_algorithm;
  41.  
    }
  42.  
     
  43.  
    void ColorMaker::setAlgorithm(GenerateAlgorithm algorithm)
  44.  
    {
  45.  
    m_algorithm = algorithm;
  46.  
    }
  47.  
     
  48.  
    void ColorMaker::start()
  49.  
    {
  50.  
    if(m_nColorTimer == 0)
  51.  
    {
  52.  
    m_nColorTimer = startTimer(1000);
  53.  
    }
  54.  
    }
  55.  
     
  56.  
    void ColorMaker::stop()
  57.  
    {
  58.  
    if(m_nColorTimer > 0)
  59.  
    {
  60.  
    killTimer(m_nColorTimer);
  61.  
    m_nColorTimer = 0;
  62.  
    }
  63.  
    }
  64.  
     
  65.  
    void ColorMaker::timerEvent(QTimerEvent *e)
  66.  
    {
  67.  
    if(e->timerId() == m_nColorTimer)
  68.  
    {
  69.  
    switch(m_algorithm)
  70.  
    {
  71.  
    case RandomRGB:
  72.  
    m_currentColor.setRgb(qrand() % 255, qrand() % 255, qrand() % 255);
  73.  
    break;
  74.  
    case RandomRed:
  75.  
    m_currentColor.setRed(qrand() % 255);
  76.  
    break;
  77.  
    case RandomGreen:
  78.  
    m_currentColor.setGreen(qrand() % 255);
  79.  
    break;
  80.  
    case RandomBlue:
  81.  
    m_currentColor.setBlue(qrand() % 255);
  82.  
    break;
  83.  
    case LinearIncrease:
  84.  
    {
  85.  
    int r = m_currentColor.red() + 10;
  86.  
    int g = m_currentColor.green() + 10;
  87.  
    int b = m_currentColor.blue() + 10;
  88.  
    m_currentColor.setRgb(r % 255, g % 255, b % 255);
  89.  
    }
  90.  
    break;
  91.  
    }
  92.  
    emit colorChanged(m_currentColor);
  93.  
    emit currentTime(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
  94.  
    }
  95.  
    else
  96.  
    {
  97.  
    QObject::timerEvent(e);
  98.  
    }
  99.  
    }

我使用一个周期为 1000 的定时器来产生颜色,定时器触发时根据算法来构造新的颜色值,发射 colorChanged 信号,同时也发送一个 currentTime 信号。

注册一个 QML 中可用的类型
    看过了怎样实现一个可供 QML 访问的类,这节咱们看看怎样将一个 C++ 类型注册为 QML 类型以及怎样在 QML 中使用这个类型。
    要达到这种目的,大概能够分四步:
一、实现 C++ 类
二、注册 QML 类型
三、在 QML 中导入类型
四、在 QML 建立由 C++ 导出的类型的实例并使用
    ColorMaker 已经就绪了,如今看看怎样将其注册为 QML 可使用的类型。
 

注册 QML 类型

 要注册一个 QML 类型,有多种方法可用,如 qmlRegisterSingletonType() 用来注册一个单例类型, qmlRegisterType() 注册一个非单例的类型, qmlRegisterTypeNotAvailable() 注册一个类型用来占位, qmlRegisterUncreatableType() 一般用来注册一个具备附加属性的附加类型,……好吧,我这里只说常规的类型注册,其它的,请您参考 Qt SDK 吧。
    qmlRegisterType() 是个模板函数,有两个原型:
 

  1.  
    template<typename T>
  2.  
    intqmlRegisterType(constchar *uri, int versionMajor, int versionMinor, constchar *qmlName);
  3.  
     
  4.  
    template<typename T, int metaObjectRevision>
  5.  
    intqmlRegisterType(constchar *uri, int versionMajor, int versionMinor, constchar *qmlName);


    前一个原型通常用来注册一个新类型,然后一个能够为特定的版本注册类型。后面这个牵涉到 Qt Quick 的类型和版本机制,三言两语不能尽述,我们单说前一个原型的使用。要使用 qmlRegisterType 须要包含 QtQml 头文件。
    先说模板参数 typename ,它就是你实现的 C++ 类的类名。
    qmlRegisterType() 的第一个参数 uri ,让你指定一个惟一的包名,相似 Java 中的那种,一是用来避免名字冲突,而是能够把多个相关类聚合到一个包中方便引用。好比咱们常写这个语句 "import QtQuick.Controls 1.1" ,其中的 "QtQuick.Controls" 就是包名 uri ,而 1.1 则是版本,是 versionMajor 和 versionMinor 的组合。 qmlName 则是 QML 中可使用的类名。
    下面是 colorMaker 示例的 main.cpp 文件:
 

  1.  
    #include <QtGui/QGuiApplication>
  2.  
    #include "qtquick2applicationviewer.h"
  3.  
    #include <QtQml>
  4.  
    #include "colorMaker.h"
  5.  
     
  6.  
     
  7.  
    int main(int argc, char *argv[])
  8.  
    {
  9.  
    QGuiApplication app(argc, argv);
  10.  
    qmlRegisterType<ColorMaker>("an.qt.ColorMaker", 1, 0, "ColorMaker");
  11.  
     
  12.  
    QtQuick2ApplicationViewer viewer;
  13.  
    viewer.setMainQmlFile(QStringLiteral("qml/colorMaker/main.qml"));
  14.  
    viewer.showExpanded();
  15.  
     
  16.  
    return app.exec();
  17.  
    }

 上面的代码将 ColorMaker 类注册为 QML 类 ColorMaker ,主版本为 1 ,次版本为 0 ,而我起的包名则是 an.qt.ColorMaker 。注册动做必定要放在 QML 上下文建立以前,不然的话,木有用滴。
在 QML 中导入 C++ 注册的类型
    一旦你在 C++ 中注册好了 QML 类型,就能够在 QML 文档中引入你注册的包,而后使用注册的类型。要引入包,使用 import 语句。好比要使用咱们注册的 ColorMaker 类,能够在 QML 文档中加入下面的 import 语句:
import an.qt.ColorMaker 1.0

在 QML 中建立 C++ 导入类型的实例
    引入包后,你就能够在 QML 中建立 C++ 导入类型的对象了,与 QML 内建类型的使用彻底同样。以下是建立一个 ColorMaker 实例的代码:
Rectangle {
    width: 360;
    height: 360;
    
    ColorMaker {
        id: colorMaker;
        color: Qt.green;
    }
}


    如你所见,ColorMaker 的使用与 Retangle 没什么区别。若是你想在别处引用 ColorMaker 的实例,能够给实例指定一个惟一的 id ,就像上面的代码中那样。

 

完整的 colorMaker 实例

    如何定义一个能够导出到 QML 中的 C++ 类、如何注册 QML 类型、如何在 QML 中使用 C++ 导出的类型,都介绍完了,如今来看看完整的 colorMaker 。

  1.  
    import QtQuick 2.0
  2.  
    import QtQuick.Controls 1.1
  3.  
    import an.qt.ColorMaker 1.0
  4.  
     
  5.  
    Rectangle {
  6.  
    width: 360;
  7.  
    height: 360;
  8.  
    Text {
  9.  
    id: timeLabel;
  10.  
    anchors.left: parent.left;
  11.  
    anchors.leftMargin: 4;
  12.  
    anchors.top: parent.top;
  13.  
    anchors.topMargin: 4;
  14.  
    font.pixelSize: 26;
  15.  
    }
  16.  
    ColorMaker {
  17.  
    id: colorMaker;
  18.  
    color: Qt.green;
  19.  
    }
  20.  
     
  21.  
    Rectangle {
  22.  
    id: colorRect;
  23.  
    anchors.centerIn: parent;
  24.  
    width: 200;
  25.  
    height: 200;
  26.  
    color: "blue";
  27.  
    }
  28.  
     
  29.  
    Button {
  30.  
    id: start;
  31.  
    text: "start";
  32.  
    anchors.left: parent.left;
  33.  
    anchors.leftMargin: 4;
  34.  
    anchors.bottom: parent.bottom;
  35.  
    anchors.bottomMargin: 4;
  36.  
    onClicked: {
  37.  
    colorMaker.start();
  38.  
    }
  39.  
    }
  40.  
    Button {
  41.  
    id: stop;
  42.  
    text: "stop";
  43.  
    anchors.left: start.right;
  44.  
    anchors.leftMargin: 4;
  45.  
    anchors.bottom: start.bottom;
  46.  
    onClicked: {
  47.  
    colorMaker.stop();
  48.  
    }
  49.  
    }
  50.  
     
  51.  
    function changeAlgorithm(button, algorithm){
  52.  
    switch(algorithm)
  53.  
    {
  54.  
    case0:
  55.  
    button.text = "RandomRGB";
  56.  
    break;
  57.  
    case1:
  58.  
    button.text = "RandomRed";
  59.  
    break;
  60.  
    case2:
  61.  
    button.text = "RandomGreen";
  62.  
    break;
  63.  
    case3:
  64.  
    button.text = "RandomBlue";
  65.  
    break;
  66.  
    case4:
  67.  
    button.text = "LinearIncrease";
  68.  
    break;
  69.  
    }
  70.  
    }
  71.  
     
  72.  
    Button {
  73.  
    id: colorAlgorithm;
  74.  
    text: "RandomRGB";
  75.  
    anchors.left: stop.right;
  76.  
    anchors.leftMargin: 4;
  77.  
    anchors.bottom: start.bottom;
  78.  
    onClicked: {
  79.  
    var algorithm = (colorMaker.algorithm() + 1) % 5;
  80.  
    changeAlgorithm(colorAlgorithm, algorithm);
  81.  
    colorMaker.setAlgorithm(algorithm);
  82.  
    }
  83.  
    }
  84.  
     
  85.  
    Button {
  86.  
    id: quit;
  87.  
    text: "quit";
  88.  
    anchors.left: colorAlgorithm.right;
  89.  
    anchors.leftMargin: 4;
  90.  
    anchors.bottom: start.bottom;
  91.  
    onClicked: {
  92.  
    Qt.quit();
  93.  
    }
  94.  
    }
  95.  
     
  96.  
    Component.onCompleted: {
  97.  
    colorMaker.color = Qt.rgba(0,180,120, 255);
  98.  
    colorMaker.setAlgorithm(ColorMaker.LinearIncrease);
  99.  
    changeAlgorithm(colorAlgorithm, colorMaker.algorithm());
  100.  
    }
  101.  
     
  102.  
    Connections {
  103.  
    target: colorMaker;
  104.  
    onCurrentTime:{
  105.  
    timeLabel.text = strTime;
  106.  
    timeLabel.color = colorMaker.timeColor;
  107.  
    }
  108.  
    }
  109.  
     
  110.  
    Connections {
  111.  
    target: colorMaker;
  112.  
    onColorChanged:{
  113.  
    colorRect.color = color;
  114.  
    }
  115.  
    }
  116.  
    }

    main.qml 的界面分红了三部分,参看图 1 。顶部是一个 Text ,用来显示由 ColorMaker 提供的时间,我使用 Connections 对象,指定 target 为 colorMaker ,在 onCurrentTime 信号处理器中改变 timeLabel 的文本和颜色。这里使用 ColorMaker 的 timeColor 属性,该属性的读取函数是 timeColor ,回看一下 colorMaker.cpp 中的实现:
 

  1.  
    QColor ColorMaker::timeColor() const
  2.  
    {
  3.  
    QTime time = QTime::currentTime();
  4.  
    int r = time.hour();
  5.  
    int g = time.minute()*2;
  6.  
    int b = time.second()*4;
  7.  
    return QColor::fromRgb(r, g, b);
  8.  
    }

   timeColor() 函数获取当前时间,取时、分、秒转换为 R 、 G 、 B 值,构造一个 QColor 对象。
    我构造了ColorMaker 类的一个实例, id 为 colorMaker ,初始化颜色值为 green 。
    colorMaker 实例界面的中间是一个 Rectangle 对象,id 是 colorRect 。我使用 Connections 对象,指定 target 为 colorMaker ,在 onColorChanged 信号处理器中改变 colorRect 的颜色。
    界面的底部就是几个按钮,使用锚布局把它们排成一行。 start 按钮的 onClicked 信号处理器调用 colorMaker 的 start() 槽,启动颜色生成器。 stop 按钮的 onClicked 信号处理器调用 colorMaker 的 stop() 槽,中止颜色生成器。而 colorAlgorithm 按钮则每点击一次就切换一个颜色生成算法,同时调用 changeAlgorithm() 函数,根据算法改变按钮上的文字。 quit 按钮一点就退出应用。
    main.qml 还引入了一个新内容:定义函数。这个能够参考 JavaScript 的教程。咱们定义的 changeAlgorithm 函数,接受两个参数, button 和 algorithm 。若是你是 C++ 程序猿,可能有点儿不适应:怎么参数就木有类型呢哈…… JavaScript 就是酱紫滴,拥有动态类型,一个变量在赋值时决定其类型。
    这就是 colorMaker 的所有了。
 

 

 

导出一个 C++ 对象为 QML 的属性
    上面看了怎样导出一个 QML 类型在 QML 文档中使用,你还能够把 C++ 中建立的对象做为属性传递到 QML 环境中,而后在 QML 环境中访问。咱们仍是以 colorMaker 为例,对其代码作适当修改来适应本节的内容。
注册属性
    要将一个对象注册为属性很简单,colorMaker 的 main.cpp 修改后以下:
 

  1.  
    #include <QtGui/QGuiApplication>
  2.  
    #include "qtquick2applicationviewer.h"
  3.  
    #include <QtQml>
  4.  
    #include "colorMaker.h"
  5.  
     
  6.  
     
  7.  
    int main(int argc, char *argv[])
  8.  
    {
  9.  
    QGuiApplication app(argc, argv);
  10.  
     
  11.  
    QtQuick2ApplicationViewer viewer;
  12.  
     
  13.  
    viewer.rootContext()->setContextProperty("colorMaker", new ColorMaker);
  14.  
     
  15.  
    viewer.setMainQmlFile(QStringLiteral("qml/colorMaker/main.qml"));
  16.  
    viewer.showExpanded();
  17.  
     
  18.  
    return app.exec();
  19.  
    }

正式这行代码从堆上分配了一个 ColorMaker 对象,而后注册为 QML 上下文的属性,起了个名字就叫 colorMaker 。
    viewer.rootContext() 返回的是 QQmlContext 对象。 QQmlContext 类表明一个 QML 上下文,它的 setContextProperty() 方法能够为该上下文设置一个全局可见的属性。要注意的是,你 new 出来的对象, QQmlContext 只是使用,不会帮你删除,你须要本身找一个合适的时机来删除它。
    还有一点要说明,由于咱们去掉了 qmlRegisterType() 调用,因此在 main.qml 中不能再访问 ColorMaker 类了,好比你不能经过类名来引用它定义的 GenerateAlgorithm 枚举类型, colorMaker.setAlgorithm(ColorMaker.LinearIncrease) 语句会致使下面的报错:
ReferenceError: ColorMaker is not defined
 

 

    如今来看如何在 QML 中使用咱们导出的属性

在 QML 中使用关联到 C++ 对象的属性

    一旦调用 setContextProperty() 导出了属性,就能够在 QML 中使用了,不须要 import 语句哦。下面是 main.qml 修改后的代码:

 

  1.  
    import QtQuick 2.0
  2.  
    import QtQuick.Controls 1.1
  3.  
    //[1]
  4.  
    //import an.qt.ColorMaker 1.0
  5.  
     
  6.  
    Rectangle {
  7.  
    width: 360;
  8.  
    height: 360;
  9.  
    Text {
  10.  
    id: timeLabel;
  11.  
    anchors.left: parent.left;
  12.  
    anchors.leftMargin: 4;
  13.  
    anchors.top: parent.top;
  14.  
    anchors.topMargin: 4;
  15.  
    font.pixelSize: 26;
  16.  
    }
  17.  
    /* [2]
  18.  
    ColorMaker {
  19.  
    id: colorMaker;
  20.  
    color: Qt.green;
  21.  
    }
  22.  
    */
  23.  
     
  24.  
    Rectangle {
  25.  
    id: colorRect;
  26.  
    anchors.centerIn: parent;
  27.  
    width: 200;
  28.  
    height: 200;
  29.  
    color: "blue";
  30.  
    }
  31.  
     
  32.  
    Button {
  33.  
    id: start;
  34.  
    text: "start";
  35.  
    anchors.left: parent.left;
  36.  
    anchors.leftMargin: 4;
  37.  
    anchors.bottom: parent.bottom;
  38.  
    anchors.bottomMargin: 4;
  39.  
    onClicked: {
  40.  
    colorMaker.start();
  41.  
    }
  42.  
    }
  43.  
    Button {
  44.  
    id: stop;
  45.  
    text: "stop";
  46.  
    anchors.left: start.right;
  47.  
    anchors.leftMargin: 4;
  48.  
    anchors.bottom: start.bottom;
  49.  
    onClicked: {
  50.  
    colorMaker.stop();
  51.  
    }
  52.  
    }
  53.  
     
  54.  
    function changeAlgorithm(button, algorithm){
  55.  
    switch(algorithm)
  56.  
    {
  57.  
    case0:
  58.  
    button.text = "RandomRGB";
  59.  
    break;
  60.  
    case1:
  61.  
    button.text = "RandomRed";
  62.  
    break;
  63.  
    case2:
  64.  
    button.text = "RandomGreen";
  65.  
    break;
  66.  
    case3:
  67.  
    button.text = "RandomBlue";
  68.  
    break;
  69.  
    case4:
  70.  
    button.text = "LinearIncrease";
  71.  
    break;
  72.  
    }
  73.  
    }
  74.  
     
  75.  
    Button {
  76.  
    id: colorAlgorithm;
  77.  
    text: "RandomRGB";
  78.  
    anchors.left: stop.right;
  79.  
    anchors.leftMargin: 4;
  80.  
    anchors.bottom: start.bottom;
  81.  
    onClicked: {
  82.  
    var algorithm = (colorMaker.algorithm() + 1) % 5;
  83.  
    changeAlgorithm(colorAlgorithm, algorithm);
  84.  
    colorMaker.setAlgorithm(algorithm);
  85.  
    }
  86.  
    }
  87.  
     
  88.  
    Button {
  89.  
    id: quit;
  90.  
    text: "quit";
  91.  
    anchors.left: colorAlgorithm.right;
  92.  
    anchors.leftMargin: 4;
  93.  
    anchors.bottom: start.bottom;
  94.  
    onClicked: {
  95.  
    Qt.quit();
  96.  
    }
  97.  
    }
  98.  
     
  99.  
    Component.onCompleted: {
  100.  
    colorMaker.color = Qt.rgba(0,180,120, 255);
  101.  
    //[3]
  102.  
    //colorMaker.setAlgorithm(ColorMaker.LinearIncrease);
  103.  
    colorMaker.setAlgorithm(2);
  104.  
    changeAlgorithm(colorAlgorithm, colorMaker.algorithm());
  105.  
    }
  106.  
     
  107.  
    Connections {
  108.  
    target: colorMaker;
  109.  
    onCurrentTime:{
  110.  
    timeLabel.text = strTime;
  111.  
    timeLabel.color = colorMaker.timeColor;
  112.  
    }
  113.  
    }
  114.  
     
  115.  
    Connections {
  116.  
    target: colorMaker;
  117.  
    onColorChanged:{
  118.  
    colorRect.color = color;
  119.  
    }
  120.  
    }
  121.  
    }

 

main.qml 代码主要修改了三处,我已经使用方括号标注出来了。由于我将导出的属性命名为 colorMaker ,和导出 ColorMaker 类时构造的实例的 id 同样,因此改动少了些。
    你看到了,导出的属性能够直接使用,与属性关联的对象,它的信号、槽、可调用方法(使用 Q_INVOKABLE 宏修饰的方法)、属性均可以使用,只是不能经过类名来引用枚举值了。
 

 

在 C++ 中使用 QML 对象
    看过了如何在 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() 。它们的函数原型以下:
 

  1.  
    T QObject::findChild(const QString & name = QString(),\
  2.  
    Qt::FindChildOptions options = \
  3.  
    Qt::FindChildrenRecursively) const;
  4.  
    QList<T> QObject::findChildren(const QString & name = \
  5.  
    QString(), Qt::FindChildOptions options = \
  6.  
    Qt::FindChildrenRecursively) const;
  7.  
    QList<T> QObject::findChildren(const QRegExp & regExp, \
  8.  
    Qt::FindChildOptions options = \
  9.  
    Qt::FindChildrenRecursively) const;
  10.  
    QList<T> QObject::findChildren(const QRegularExpression & re,\
  11.  
    Qt::FindChildOptions options = \
  12.  
    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, 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]
 

其实 QMetaObject 还有三个 invokeMethod() 函数,不过都是上面这个原型的重载,因此咱们只要介绍上面这个就 OK 了。
    先说返回值吧,返回 true 说明调用成功。返回 false ,要么是由于没有你说的那个方法,要么是参数类型不匹配。
    第一个参数是被调用对象的指针。
    第二个参数是方法名字。
    第三个参数是链接类型,看到这里你就知道, 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 对象,那么你能够这么调用(同步方式):

  1.  
    QString retVal;
  2.  
    QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
  3.  
    Q_RETURN_ARG(QString, retVal),
  4.  
    Q_ARG(QString, "sqrt"),
  5.  
    Q_ARG(int, 42),
  6.  
    Q_ARG(double, 9.7));

若是你要让一个线程对象退出,能够这么调用(队列链接方式):

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

callQml 示例

 

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

  1.  
    import QtQuick 2.0
  2.  
    import QtQuick.Controls 1.1
  3.  
     
  4.  
    Rectangle {
  5.  
    objectName: "rootRect";
  6.  
    width: 360;
  7.  
    height: 360;
  8.  
    Text {
  9.  
    objectName: "textLabel";
  10.  
    text: "Hello World";
  11.  
    anchors.centerIn: parent;
  12.  
    font.pixelSize: 26;
  13.  
    }
  14.  
     
  15.  
    Button {
  16.  
    anchors.right: parent.right;
  17.  
    anchors.rightMargin: 4;
  18.  
    anchors.bottom: parent.bottom;
  19.  
    anchors.bottomMargin: 4;
  20.  
    text: "quit";
  21.  
    objectName: "quitButton";
  22.  
    }
  23.  
    }

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

    如今来看看 main.cpp :

  1.  
    #include <QtGui/QGuiApplication>
  2.  
    #include "qtquick2applicationviewer.h"
  3.  
    #include <QQuickItem>
  4.  
    #include "changeColor.h"
  5.  
    #include <QMetaObject>
  6.  
    #include <QDebug>
  7.  
    #include <QColor>
  8.  
    #include <QVariant>
  9.  
     
  10.  
    int main(int argc, char *argv[])
  11.  
    {
  12.  
    QGuiApplication app(argc, argv);
  13.  
     
  14.  
    QtQuick2ApplicationViewer viewer;
  15.  
    viewer.setMainQmlFile(QStringLiteral("qml/callQml/main.qml"));
  16.  
    viewer.showExpanded();
  17.  
     
  18.  
    QQuickItem * rootItem = viewer.rootObject();
  19.  
    new ChangeQmlColor(rootItem);
  20.  
    QObject * quitButton = rootItem->findChild<QObject*>("quitButton");
  21.  
    if(quitButton)
  22.  
    {
  23.  
    QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
  24.  
    }
  25.  
     
  26.  
    QObject *textLabel = rootItem->findChild<QObject*>("textLabel");
  27.  
    if(textLabel)
  28.  
    {
  29.  
    //1. failed call
  30.  
    bool bRet = QMetaObject::invokeMethod(textLabel, "setText", Q_ARG(QString, "world hello"));
  31.  
    qDebug() << "call setText return - " << bRet;
  32.  
    textLabel->setProperty("color", QColor::fromRgb(255,0,0));
  33.  
    bRet = QMetaObject::invokeMethod(textLabel, "doLayout");
  34.  
    qDebug() << "call doLayout return - " << bRet;
  35.  
    }
  36.  
     
  37.  
    return app.exec();
  38.  
    }

    在一开始我经过 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() ,此次成功了。

            图 2 callQml 运行效果图
    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 类定义以下:

  1.  
    #ifndef CHANGECOLOR_H
  2.  
    #define CHANGECOLOR_H
  3.  
    #include <QObject>
  4.  
    #include <QTimer>
  5.  
     
  6.  
    classChangeQmlColor :public QObject
  7.  
    {
  8.  
    Q_OBJECT
  9.  
    public:
  10.  
    ChangeQmlColor(QObject *target, QObject *parent = 0);
  11.  
    ~ChangeQmlColor();
  12.  
     
  13.  
    protected slots:
  14.  
    voidonTimeout();
  15.  
     
  16.  
    private:
  17.  
    QTimer m_timer;
  18.  
    QObject *m_target;
  19.  
    };
  20.  
     
  21.  
    #endif

 实现文件 changeColor.cpp :

  1.  
    #include "changeColor.h"
  2.  
    #include <QDateTime>
  3.  
    #include <QColor>
  4.  
    #include <QVariant>
  5.  
     
  6.  
    ChangeQmlColor::ChangeQmlColor(QObject *target, QObject *parent)
  7.  
    : QObject(parent)
  8.  
    , m_timer(this)
  9.  
    , m_target(target)
  10.  
    {
  11.  
    qsrand(QDateTime::currentDateTime().toTime_t());
  12.  
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
  13.  
    m_timer.start(1000);
  14.  
    }
  15.  
     
  16.  
    ChangeQmlColor::~ChangeQmlColor()
  17.  
    {}
  18.  
     
  19.  
    void ChangeQmlColor::onTimeout()
  20.  
    {
  21.  
    QColor color = QColor::fromRgb(qrand()%256, qrand()%256, qrand()%256);
  22.  
    m_target->setProperty("color", color);
  23.  
    }

 

---------------------------------------------------------------------------------------------------------------------------------------------------

 

QWebEngine打开chrome devtool调试工具

在代码中加入下面这段

//QWebEngine DEBUG --remote-debugging-port=9223
qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "9223");

加入load qml前添加
在浏览器中访问http://localhost:9223/ 便可进行调试

 

---------------------------------------------------------------------------------------------------------------------------------------------------

(译)经过WebChannel/WebSockets与QML中的HTML交互 1、前言

Qt容许使用所谓的混合GUI建立应用程序——在这种GUI中,能够将本机部件与基于html的内容混合在一块儿。经过WebChannelWebSockets公开QObject,这种混合甚至支持这些本地部分和html端之间的交互。

 

2、如何显示HTML内容

  1. 使用webEngineView
  2. 使用webView
  3. 使用独立的Web浏览器(不会集成到应用程序中);

这三种方法以不一样的方式进行,但都支持QML和HTML之间的通讯。

确切的说,WebEngineView以一种方式完成,而WebView(就像网络浏览器同样)以另外一种方式完成。WebEngineView和WebView是两码事。

(1)webEngineView

WebEngineView是由Qt本身基于Chromium (Qt WebEngine)的web浏览器引擎提供的web视图。它是一个功能齐全的web浏览器,与Qt捆绑并集成在一块儿,这很好,但同时这意味着您须要将它与您的应用程序一块儿拖动,这是一个至关大的东西。

(2)webView

WebView是一个web视图,但不一样之处在于它使用平台的本地web浏览器(若是可用的话),所以它不须要将完整的web浏览器堆栈做为应用程序的一部分(WebEngineView就是这种状况),所以您的应用程序更轻量级。另外一点是,有些平台根本不容许任何非系统的web浏览器,所以WebView是惟一可用的选项。

(3)webEngineView 和 webView的区别

根据本文,WebEngineView和WebView的关键区别在于Qt如何与这些视图中的html内容通讯。因为Chromium IPC功能,WebEngineView提供了最简单的方式-直接经过WebChannel,。而WebView(以及外部web浏览器)要求您首先为WebChannel创建一些传输。

3、与QML中的HTML交互

好的,咱们能够显示HTML,可是如何从QML与之交互呢?一切都经过WebChannel。在HTML端,它是经过特殊的JavaScript库- Qt WebChannel JavaScript API完成的。

(1)WebEngineView - 直接使用WebChannel

WebEngineView能够直接使用WebChannel,以这个存储库为基础进行讲解。

main.qml

 
// 一个具备属性、信号和方法的对象——就像任何普通的Qt对象同样
QtObject {
    id: someObject

    // ID,在这个ID下,这个对象在WebEngineView端是已知的
    WebChannel.id: "backend"

    property string someProperty: "Break on through to the other side"

    signal someSignal(string message);

    function changeText(newText) {
        txt.text = newText;
        return "New text length: " + newText.length;
    }
}

Text {
    id: txt
    text: "Some text"
    onTextChanged: {
        // 此信号将在WebEngineView端触发一个函数(若是链接的话)
        someObject.someSignal(text)
    }
}

WebEngineView {
    url: "qrc:/index.html"
    webChannel: channel
}

WebChannel {
    id: channel
    registeredObjects: [someObject]
}复制代码
 

这里咱们建立WebChannel并将其ID分配给WebEngineView,并在通道上注册QtObject的ID。固然,您能够从c++端“注入”一个c++ /Qt对象,而不是在QML端定义的QtObject。

index.html

 
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>

<script type="text/javascript">
    // 这是QML端的QtObject
    var backend;

    window.onload = function()
    {
        new QWebChannel(qt.webChannelTransport, function(channel) {
            // 在channel.object下,全部发布的对象在通道中都是可用的
            // 在附加的WebChannel.id属性中设置的标识符。
            backend = channel.objects.backend;

            //链接信号
            backend.someSignal.connect(function(someText) {
                alert("Got signal: " + someText);
                document.getElementById("lbl").innerHTML = someText;
            });
        });
    }

    // 演示异步交互
    var result = "ololo";
    function changeLabel()
    {
        var textInputValue = document.getElementById("input").value.trim();
        if (textInputValue.length === 0)
        {
            alert("You haven't entered anything!");
            return;
        }

        // 调用方法并接收返回值
        backend.changeText(textInputValue, function(callback) {
            result = callback;
            // 因为它是异步的,所以稍后将出现此警报并显示实际结果
            alert(result);
            // 将变量重置为默认值
            result = "ololo";
        });
        // 此警告将首先出现,并显示默认的“ololo”
        alert(result);
    }

    // 您还能够从QML端读取/写入QtObject的属性
    function getPropertyValue()
    {
        var originalValue = backend.someProperty;

        alert(backend.someProperty);
        backend.someProperty = "some another value";
        alert(backend.someProperty);

        backend.someProperty = originalValue;
    }
</script>复制代码
 

在这里,您须要在windows.onload事件上建立一个QWebChannel并获取后端对象。以后,您能够调用它的方法,链接到它的信号并访问它的属性。

下面是一个简单的例子,演示了QML(蓝色矩形外的全部内容)和HTML(蓝色矩形内的部分)之间的通讯:

这是它的模式:

注意,交互是异步完成的——查看changeLabel()函数并注意警报的顺序。

(2)WebView - WebSockets上的WebChannel

WebView(和外部Web浏览器)没法直接使用WebChannel。您须要首先建立一个WebSockets传输,而后在其上使用WebChannel。

这仅使用QML是没法实现的,所以您还必须编写一些C ++代码。这有点使人沮丧,但更使人沮丧的是文档没有明确提到它。

因此,当我发现这一点时,我决定重写一个C ++示例。当我差很少完成时,我也获得了Stack Overflow的答案,几乎展现了如何在QML中作的全部事情,我最终获得了两个解决方案,以下。

(a)主要是c++完成

这个函数的大部分工做都是用c++完成的,QML没用什么。

main.cpp

 
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    // 不要忘记这个
    QtWebView::initialize();

    QWebSocketServer server(
                QStringLiteral("WebSockets example"),
                QWebSocketServer::NonSecureMode
                );
    if (!server.listen(QHostAddress::LocalHost, 55222)) { return 1; }

    // 在QWebChannelAbstractTransport对象中包装WebSocket客户端
    WebSocketClientWrapper clientWrapper(&server);

    // 设置通道
    QWebChannel channel;
    QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
                     &channel, &QWebChannel::connectTo);

    // 设置核心并将其发布到QWebChannel
    Backend *backend = new Backend();
    channel.registerObject(QStringLiteral("backend"), backend);

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("someObject", backend);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty()) { return -1; }

    return app.exec();
}复制代码
 

这里最重要的是WebSocketClientWrapper(WebSocketTransport在下面使用)。这是必须本身实现的,而文档的帮助不大。

使用WebSocketClientWrapper,您最终能够链接QWebChannel并注册您的对象(在个人例子中是Backend,尽管我保留了相同的ID - someObject),所以它将在HTML端可用。

注意,此次我须要注册一个已经建立的c++对象(不是类型),因此我使用setContextProperty

index.html

 
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>

<script type="text/javascript">
    // 这是QML端的QtObject
    var backend;

    window.onload = function()
    {
        var socket = new WebSocket("ws://127.0.0.1:55222");

        socket.onopen = function()
        {
            new QWebChannel(socket, function(channel) {
                backend = channel.objects.backend;

                // 链接信号
                backend.someSignal.connect(function(someText) {
                    alert("Got signal: " + someText);
                    document.getElementById("lbl").innerHTML = someText;
                });
            });
        };
    }
</script>复制代码
 

与WebEngineView示例中的index.html不一样,这里首先须要创建WebSocket链接,做为QWebChannel的传输。其他的都是同样的。

main.qml

 
Text {
    id: txt
    text: "Some text"
    onTextChanged: {
        someObject.someSignal(text)
    }
    Component.onCompleted: {
         someObject.textNeedsToBeChanged.connect(changeText)
    }
    function changeText(newText) {
        txt.text = newText;
    }
}

WebView {
    id: webView
    url: "qrc:/index.html"
}复制代码
 

QML代码也有一点不一样。首先,someObject这是一个上下文属性,所以不须要导入和声明它。其次,c++对象和QML组件之间的交互须要再添加一个信号(textNeedsToBeChanged)。

所以,交互模式也变得有点奇怪:

幸运的是,有一个更好的解决方案。下面就是。

(b)主要是QML

我更喜欢这个例子,由于它主要在QML中完成,C ++上只有一点点。我是在Stack Overflow上获得的这个答案

首先,咱们须要实现WebChannel的传输

websockettransport.h

 
class WebSocketTransport : public QWebChannelAbstractTransport
{
    Q_OBJECT

public:
    Q_INVOKABLE void sendMessage(const QJsonObject &message) override
    {
        QJsonDocument doc(message);
        emit messageChanged(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
    }

    Q_INVOKABLE void textMessageReceive(const QString &messageData)
    {
        QJsonParseError error;
        QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);
        if (error.error)
        {
            qWarning() << "Failed to parse text message as JSON object:" << messageData
                       << "Error is:" << error.errorString();
            return;
        } else if (!message.isObject())
        {
            qWarning() << "Received JSON message that is not an object: " << messageData;
            return;
        }
        emit messageReceived(message.object(), this);
    }

signals:
    void messageChanged(const QString & message);
};复制代码
 

而后将其注册到QML

main.cpp

 
#include "websockettransport.h"

int main(int argc, char *argv[])
{
    // ...
    
    qmlRegisterType("io.decovar.WebSocketTransport", 1, 0, "WebSocketTransport");

    // ...
}复制代码
 

剩下的都在QML

main.qml

 
import io.decovar.WebSocketTransport 1.0

// ...

// 一个具备属性、信号和方法的对象——就像任何普通的Qt对象同样
QtObject {
    id: someObject

    // ID,在这个ID下,这个对象在WebEngineView端是已知的
    WebChannel.id: "backend"

    property string someProperty: "Break on through to the other side"

    signal someSignal(string message);

    function changeText(newText) {
        txt.text = newText;
        return "New text length: " + newText.length;
    }
}

WebSocketTransport {
    id: transport
}

WebSocketServer {
    id: server
    listen: true
    port: 55222
    onClientConnected: {
        if(webSocket.status === WebSocket.Open) {
            channel.connectTo(transport)
            webSocket.onTextMessageReceived.connect(transport.textMessageReceive)
            transport.onMessageChanged.connect(webSocket.sendTextMessage)
        }
    }
}

Text {
    id: txt
    text: "Some text"
    onTextChanged: {
        //此信号将在WebView端触发一个函数(若是链接)
        someObject.someSignal(text)
    }
}

WebView {
    url: "qrc:/index.html"
}

WebChannel {
    id: channel
    registeredObjects: [someObject]
}复制代码
 

index.html与前面的例子相同,建立一个WebSocket并将其用做QWebChannel的传输。

顺便说一下,正如我在前面提到的,WebView和独立/外部浏览器是同样的,因此您能够在web浏览器中打开index.html,它将以相同的方式工做-只是不要忘记从代码中删除qrc:/并复制qwebchannel.js到相同的文件夹。

在这个存储库中能够找到这三个示例的完整源代码。

4、后话-关于文档

尽管WebChannel和WebSockets都有超过5个例子,但很难理解它是如何工做的?为何没有一个让它与QML一块儿工做的例子?

如今,关于qwebchannel.js。看一下文档页面的第一段:

要与QWebChannel或WebChannel通讯,客户机必须使用并设置QWebChannel .js提供的JavaScript API。对于运行在Qt WebEngine中的客户机,能够经过qrc:///qtwebchannel/qwebchannel.js加载文件。对于外部客户端,须要将文件复制到web服务器。

所以,对于集成的web视图,咱们可使用一个特殊的资源qrc:///qtwebchannel/qwebchannel。可是咱们在哪里能够为外部客户端找到这个文件呢?是的,这个文件在这个或其余任何页面上都找不到。幸运的是,你能够从如下例子中找到答案:

QWebChannelAbstractTransport的文档页面也不是一个详细的页面,由于它没有一行代码,更不用说示例了。它对于WebChannel的必要性是这样不经意间被简单说起的:

请注意,只要将QWebChannel链接到QWebChannelAbstractTransport,它就能够彻底运行。

基本上,若是不是我找到的存储库以及在Stack Overflow上得到的帮助 - 我根本没法进行一切工做。

相关文章
相关标签/搜索