来源:经过WebChannel/WebSockets与QML中的HTML交互javascript
GitHub:八至html
做者:狐狸家的鱼java
本文连接:QML与HTML交互c++
在查询QML与HTML之间通讯交互时资料不多,这篇文章讲解的比较清楚git
Qt容许使用所谓的混合GUI建立应用程序——在这种GUI中,能够将本机部件与基于html的内容混合在一块儿。经过WebChannel和WebSockets公开QObject,这种混合甚至支持这些本地部分和html端之间的交互。github
这三种方法以不一样的方式进行,但都支持QML和HTML之间的通讯。web
确切的说,WebEngineView以一种方式完成,而WebView(就像网络浏览器同样)以另外一种方式完成。WebEngineView和WebView是两码事。windows
(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创建一些传输。
好的,咱们能够显示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上得到的帮助 - 我根本没法进行一切工做。