(譯)通過WebChannel/WebSockets與QML中的HTML互動

sueRimn發表於2019-01-29

來源:通過WebChannel/WebSockets與QML中的HTML互動

GitHub:八至

作者:狐狸家的魚

本文連結:QML與HTML互動

在查詢QML與HTML之間通訊互動時資料很少,這篇文章講解的比較清楚

一、前言

Qt允許使用所謂的混合GUI建立應用程式——在這種GUI中,可以將本機部件與基於html的內容混合在一起。通過WebChannelWebSockets公開QObject,這種混合甚至支援這些本地部分和html端之間的互動。

二、如何顯示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建立一些傳輸。

三、與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_OBJECTpublic: 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到相同的資料夾。

在這個儲存庫中可以找到這三個示例的完整原始碼。

四、後話-關於文件

儘管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上獲得的幫助 – 我根本無法進行一切工作。

來源:https://juejin.im/post/5c4fab726fb9a049eb3c5411#comment

相關文章