來源:經過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上得到的幫助 - 我根本沒法進行一切工做。