QT QML HTML通信
使用 Qt WebChannel 实现 C++/QML 和 HTML 页面之间交互
在项目开发中,常常会有在原生应用程序中嵌入 HTML 页面或者 Web 项目,并且需要应用程序与所加载的 HTML 页面的相互通信的需求。
本篇文章基于 Qt 框架,讲解如何使用 Qt WebChannel 实现 C++/QML 和 HTML 页面之间交互,包括:
- 在 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 是完全异步的。
Qt WebChannel 支持服务端(QML/C++ 应用程序)和客户端(HTML/JavaScript 或 QML 应用程序)之间的点对点(peer-to-peer)通信。它提供了一个 JavaScript 库,用于将 C++ 和 QML 应用程序与 HTML/JavaScript 和 QML 客户端无缝集成。客户端必须使用 JavaScript 库来访问主机端应用程序发布的序列化的 QObjects 对象。
注:本文中提到的客户端和服务端其实是在同一个应用程序内,因为 WebChannel 是基于 WebSocket 实现的,所以会有这种客户端和服务端的叫法。
QML/HTML 混合应用程序
本节演示 QML 应用程序和 HTML 之间如何进行交互。
本应用用到了 WebChannel 和 WebEngine 两个主要模块,所以要将下面这行添加到 qmake .pro 文件中:
QT += webchannel webengine
QML 服务端
在 QML 服务端,首先导入 Qt WebChannel 模块,以及 Qt WebEngine 模块:
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 客户端中:
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++ 对象的槽或信号……所以,这是最简单好用的一种途径。
-
class ColorMaker : public QObject
-
{
-
Q_OBJECT
-
-
public:
-
ColorMaker(QObject *parent = 0);
-
~ColorMaker();
-
-
signals:
-
void colorChanged(const QColor & color);
-
void currentTime(const QString &strTime);
-
-
public slots:
-
void start();
-
void stop();
-
-
};
我们定义了 start() / stop() 两个槽, colorChanged() / currentTime() 两个信号,都可以在 QML 中使用。
Q_INVOKABLE 宏
在定义一个类的成员函数时使用 Q_INVOKABLE 宏来修饰,就可以让该方法被元对象系统调用。这个宏必须放在返回类型前面。
-
-
class ColorMaker : public QObject
-
{
-
Q_OBJECT
-
-
public:
-
ColorMaker(QObject *parent = 0);
-
~ColorMaker();
-
-
Q_INVOKABLE GenerateAlgorithm algorithm() const;
-
Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);
-
-
signals:
-
void colorChanged(const QColor & color);
-
void currentTime(const QString &strTime);
-
-
public slots:
-
void start();
-
void stop();
-
一旦你使用 Q_INVOKABLE 将某个方法注册到元对象系统中,在 QML 中就可以用 ${Object}.${method} 来访问,colorMaker 的 main.qml 中有使用 algorithm() 和 setAlgorithm() 的 QML 代码 :
-
Component.onCompleted: {
-
colorMaker.color = Qt.rgba(0,180,120, 255);
-
colorMaker.setAlgorithm(ColorMaker.LinearIncrease);
-
changeAlgorithm(colorAlgorithm, colorMaker.algorithm());
-
}
Q_ENUMS
如果你要导出的类定义了想在 QML 中使用枚举类型,可以使用 Q_ENUMS 宏将该枚举注册到元对象系统中。
ColorMaker 类定义了 GenerateAlgorithm 枚举类型,支持 RandomRGB / RandomRed 等颜色生成算法。现在 ColorMaker 类的声明变成了这个样子:
-
class ColorMaker : public QObject
-
{
-
Q_OBJECT
-
Q_ENUMS(GenerateAlgorithm)
-
-
public:
-
ColorMaker(QObject *parent = 0);
-
~ColorMaker();
-
-
enum GenerateAlgorithm{
-
RandomRGB,
-
RandomRed,
-
RandomGreen,
-
RandomBlue,
-
LinearIncrease
-
};
-
-
Q_INVOKABLE GenerateAlgorithm algorithm() const;
-
Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);
-
-
signals:
-
void colorChanged(const QColor & color);
-
void currentTime(const QString &strTime);
-
-
public slots:
-
void start();
-
void stop();
-
};
一旦你使用 Q_ENUMS 宏注册了你的枚举类型,在 QML 中就可以用 ${CLASS_NAME}.${ENUM_VALUE} 的形式来访问,比如 ColorMaker.LinearIncrease ,上节展示的 QML 代码片段已经使用了导出的枚举类型。
Q_PROPERTY
Q_PROPERTY 宏用来定义可通过元对象系统访问的属性,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。要想使用 Q_PROPERTY 宏,你的类必须是 QObject 的后裔,必须在类首使用 Q_OBJECT 宏。
下面是 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])
是不是很复杂?你可以为一个属性命名,可以设定的选项数超过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 的使用:
-
class QQuickText : public QQuickImplicitSizeItem
-
{
-
Q_OBJECT
-
Q_ENUMS(HAlignment)
-
-
-
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
-
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
-
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
-
...
-
-
public:
-
enum HAlignment { AlignLeft = Qt::AlignLeft,
-
AlignRight = Qt::AlignRight,
-
AlignHCenter = Qt::AlignHCenter,
-
AlignJustify = Qt::AlignJustify };
-
...
-
QString text() const;
-
void setText(const QString &);
-
-
QFont font() const;
-
void setFont(const QFont &font);
-
-
QColor color() const;
-
void setColor(const QColor &c);
-
...
-
};
现在给我们的 ColorMaker 类添加一些属性,以便 QML 可以获取、设置颜色值。新的 ColorMaker 类如下:
-
class ColorMaker : public QObject
-
{
-
Q_OBJECT
-
Q_ENUMS(GenerateAlgorithm)
-
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
-
Q_PROPERTY(QColor timeColor READ timeColor)
-
-
public:
-
ColorMaker(QObject *parent = 0);
-
~ColorMaker();
-
-
enum GenerateAlgorithm{
-
RandomRGB,
-
RandomRed,
-
RandomGreen,
-
RandomBlue,
-
LinearIncrease
-
};
-
-
QColor color() const;
-
void setColor(const QColor & color);
-
QColor timeColor() const;
-
-
Q_INVOKABLE GenerateAlgorithm algorithm() const;
-
Q_INVOKABLE void setAlgorithm(GenerateAlgorithm algorithm);
-
-
signals:
-
void colorChanged(const QColor & color);
-
void currentTime(const QString &strTime);
-
-
public slots:
-
void start();
-
void stop();
-
-
protected:
-
void timerEvent(QTimerEvent *e);
-
-
private:
-
GenerateAlgorithm m_algorithm;
-
QColor m_currentColor;
-
int m_nColorTimer;
-
};
现在我们的 ColorMaker 已经是一个完整的类了,有信号、有槽、有使用 Q_INVOKABLE 注册的方法,还导出了枚举类型,小小麻雀五脏俱全。
是时候看看它的实现了。翠花,上代码:
-
-
-
-
-
ColorMaker::ColorMaker(QObject *parent)
-
: QObject(parent)
-
, m_algorithm(RandomRGB)
-
, m_currentColor(Qt::black)
-
, m_nColorTimer(0)
-
{
-
qsrand(QDateTime::currentDateTime().toTime_t());
-
}
-
-
ColorMaker::~ColorMaker()
-
{
-
}
-
-
QColor ColorMaker::color() const
-
{
-
return m_currentColor;
-
}
-
-
void ColorMaker::setColor(const QColor &color)
-
{
-
m_currentColor = color;
-
emit 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::start()
-
{
-
if(m_nColorTimer == 0)
-
{
-
m_nColorTimer = startTimer(1000);
-
}
-
}
-
-
void ColorMaker::stop()
-
{
-
if(m_nColorTimer > 0)
-
{
-
killTimer(m_nColorTimer);
-
m_nColorTimer = 0;
-
}
-
}
-
-
void ColorMaker::timerEvent(QTimerEvent *e)
-
{
-
if(e->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 colorChanged(m_currentColor);
-
emit currentTime(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
-
}
-
else
-
{
-
QObject::timerEvent(e);
-
}
-
}
我使用一个周期为 1000 的定时器来产生颜色,定时器触发时根据算法来构造新的颜色值,发射 colorChanged 信号,同时也发送一个 currentTime 信号。
注册一个 QML 中可用的类型
看过了怎样实现一个可供 QML 访问的类,这节我们看看怎样将一个 C++ 类型注册为 QML 类型以及怎样在 QML 中使用这个类型。
要达到这种目的,大概可以分四步:
1、实现 C++ 类
2、注册 QML 类型
3、在 QML 中导入类型
4、在 QML 创建由 C++ 导出的类型的实例并使用
ColorMaker 已经就绪了,现在看看怎样将其注册为 QML 可以使用的类型。
注册 QML 类型
要注册一个 QML 类型,有多种方法可用,如 qmlRegisterSingletonType() 用来注册一个单例类型, qmlRegisterType() 注册一个非单例的类型, qmlRegisterTypeNotAvailable() 注册一个类型用来占位, qmlRegisterUncreatableType() 通常用来注册一个具有附加属性的附加类型,……好吧,我这里只说常规的类型注册,其它的,请您参考 Qt SDK 吧。
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 需要包含 QtQml 头文件。
先说模板参数 typename ,它就是你实现的 C++ 类的类名。
qmlRegisterType() 的第一个参数 uri ,让你指定一个唯一的包名,类似 Java 中的那种,一是用来避免名字冲突,而是可以把多个相关类聚合到一个包中方便引用。比如我们常写这个语句 "import QtQuick.Controls 1.1" ,其中的 "QtQuick.Controls" 就是包名 uri ,而 1.1 则是版本,是 versionMajor 和 versionMinor 的组合。 qmlName 则是 QML 中可以使用的类名。
下面是 colorMaker 示例的 main.cpp 文件:
-
-
-
-
-
-
-
int main(int argc, char *argv[])
-
{
-
QGuiApplication app(argc, argv);
-
qmlRegisterType<ColorMaker>("an.qt.ColorMaker", 1, 0, "ColorMaker");
-
-
QtQuick2ApplicationViewer viewer;
-
viewer.setMainQmlFile(QStringLiteral("qml/colorMaker/main.qml"));
-
viewer.showExpanded();
-
-
return app.exec();
-
}
上面的代码将 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 。
-
import QtQuick 2.0
-
import QtQuick.Controls 1.1
-
import an.qt.ColorMaker 1.0
-
-
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.start();
-
}
-
}
-
Button {
-
id: stop;
-
text: "stop";
-
anchors.left: start.right;
-
anchors.leftMargin: 4;
-
anchors.bottom: start.bottom;
-
onClicked: {
-
colorMaker.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;
-
onCurrentTime:{
-
timeLabel.text = strTime;
-
timeLabel.color = colorMaker.timeColor;
-
}
-
}
-
-
Connections {
-
target: colorMaker;
-
onColorChanged:{
-
colorRect.color = color;
-
}
-
}
-
}
main.qml 的界面分成了三部分,参看图 1 。顶部是一个 Text ,用来显示由 ColorMaker 提供的时间,我使用 Connections 对象,指定 target 为 colorMaker ,在 onCurrentTime 信号处理器中改变 timeLabel 的文本和颜色。这里使用 ColorMaker 的 timeColor 属性,该属性的读取函数是 timeColor ,回看一下 colorMaker.cpp 中的实现:
-
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);
-
}
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 修改后如下:
-
-
-
-
-
-
-
int main(int argc, char *argv[])
-
{
-
QGuiApplication app(argc, argv);
-
-
QtQuick2ApplicationViewer viewer;
-
-
viewer.rootContext()->setContextProperty("colorMaker", new ColorMaker);
-
-
viewer.setMainQmlFile(QStringLiteral("qml/colorMaker/main.qml"));
-
viewer.showExpanded();
-
-
return app.exec();
-
}
正式这行代码从堆上分配了一个 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 修改后的代码:
-
import QtQuick 2.0
-
import QtQuick.Controls 1.1
-
//[1]
-
//import an.qt.ColorMaker 1.0
-
-
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;
-
}
-
/* [2]
-
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.start();
-
}
-
}
-
Button {
-
id: stop;
-
text: "stop";
-
anchors.left: start.right;
-
anchors.leftMargin: 4;
-
anchors.bottom: start.bottom;
-
onClicked: {
-
colorMaker.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);
-
//[3]
-
//colorMaker.setAlgorithm(ColorMaker.LinearIncrease);
-
colorMaker.setAlgorithm(2);
-
changeAlgorithm(colorAlgorithm, colorMaker.algorithm());
-
}
-
-
Connections {
-
target: colorMaker;
-
onCurrentTime:{
-
timeLabel.text = strTime;
-
timeLabel.color = colorMaker.timeColor;
-
}
-
}
-
-
Connections {
-
target: colorMaker;
-
onColorChanged:{
-
colorRect.color = color;
-
}
-
}
-
}
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() 。它们的函数原型如下:
-
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, 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 对象,那么你可以这么调用(同步方式):
-
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.0
-
import QtQuick.Controls 1.1
-
-
Rectangle {
-
objectName: "rootRect";
-
width: 360;
-
height: 360;
-
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 :
-
-
-
-
-
-
-
-
-
-
int main(int argc, char *argv[])
-
{
-
QGuiApplication app(argc, argv);
-
-
QtQuick2ApplicationViewer viewer;
-
viewer.setMainQmlFile(QStringLiteral("qml/callQml/main.qml"));
-
viewer.showExpanded();
-
-
QQuickItem * rootItem = viewer.rootObject();
-
new ChangeQmlColor(rootItem);
-
QObject * quitButton = rootItem->findChild<QObject*>("quitButton");
-
if(quitButton)
-
{
-
QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
-
}
-
-
QObject *textLabel = rootItem->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() ,这次成功了。
图 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 类定义如下:
-
-
-
-
-
-
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;
-
};
-
-
实现文件 changeColor.cpp :
-
-
-
-
-
-
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);
-
}
参考: https://blog.csdn.net/u012611644/article/details/89425204
---------------------------------------------------------------------------------------------------------------------------------------------------
QWebEngine打开chrome devtool调试工具
在代码中加入下面这段
//QWebEngine DEBUG --remote-debugging-port=9223
qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "9223");
加入load qml前添加
在浏览器中访问http://localhost:9223/ 即可进行调试
---------------------------------------------------------------------------------------------------------------------------------------------------
(译)通过WebChannel/WebSockets与QML中的HTML交互
一、前言
Qt允许使用所谓的混合GUI创建应用程序——在这种GUI中,可以将本机部件与基于html的内容混合在一起。通过WebChannel和WebSockets公开QObject,这种混合甚至支持这些本地部分和html端之间的交互。
二、如何显示HTML内容
- 使用webEngineView;
- 使用webView;
- 使用独立的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建立一些传输。
三、与QML中的HTML交互
好的,我们可以显示HTML,但是如何从QML与之交互呢?一切都通过WebChannel。在HTML端,它是通过特殊的JavaScript库- Qt WebChannel JavaScript API完成的。
(1)WebEngineView - 直接使用WebChannel
WebEngineView可以直接使用WebChannel,以这个存储库为基础进行讲解。
// 一个具有属性、信号和方法的对象——就像任何普通的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。
<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没用什么。
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。
<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的传输。其余的都是一样的。
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的传输。
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
#include "websockettransport.h"
int main(int argc, char *argv[])
{
// ...
qmlRegisterType("io.decovar.WebSocketTransport", 1, 0, "WebSocketTransport");
// ...
}复制代码
剩下的都在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到相同的文件夹。
在这个存储库中可以找到这三个示例的完整源代码。
四、后话-关于文档
尽管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上获得的帮助 - 我根本无法进行一切工作。