QML 中基于 WebSockets 的 Qt WebView 和 WebChannel

Posted

技术标签:

【中文标题】QML 中基于 WebSockets 的 Qt WebView 和 WebChannel【英文标题】:Qt WebView and WebChannel over WebSockets in QML 【发布时间】:2018-07-12 19:26:54 【问题描述】:

我想从在WebView 中运行的 html 页面访问QtObject - 调用方法、读/写属性等。

据我了解,我需要在 QML 和 HTML 端之间建立WebSockets 连接,然后将其用作WebChannel 的传输。

不要将 WebView 与 WebEngineView 混淆 - 我知道如何使用 WebEngineView 来做到这一点,但我需要使用 WebView 来做到这一点。

所以,这就是我所拥有的。

QML 端

QtObject 
    id: someObject
    WebChannel.id: "backend"
    property string someProperty: “property value"


WebSocketServer 
    listen: true
    port: 55222
    onClientConnected: 
        console.log(webSocket.status);
        //webSocket.onTextMessageReceived.connect(function(message) 
        //    console.log(qsTr("Server received message: %1").arg(message));
        //);
    


WebView 
    url: "index.html"
    //webChannel: channel // invalid property name "webChannel"
    //experimental.webChannel.registeredObjects: [someObject] // invalid property name "experimental"


WebChannel 
    id: channel
    registeredObjects: [someObject]

HTML 端

window.onload = function()

    // here will be QtObject from QML side
    var backend;

    var socket = new WebSocket("ws://localhost:55222");
    socket.onopen = function()
    
        //socket.send("some message");
        new QWebChannel(socket, function(channel) 
            backend = channel.objects.backend;
        );
    ;


function alertProperty()

    alert(backend.someProperty);

简单的消息交换工作正常(socket.send()),所以传输没问题,但是如何将 WebChannel 分配给 WebView?使用 WebEngineView 很简单,那里有一个 webChannel 属性(甚至不需要使用 WebSockets),但在 WebView 中没有任何相似之处。我的意思是,something 必须告诉 WebView WebChannel 包含我的 QtObject,以便 JS 可以看到它?

如果 WebView 不支持 WebChannel(?),那么如何使用外部浏览器呢? This example 表明使用 C++ 是可能的,但我想使用 QML。

我使用 Qt 5.11.1。

【问题讨论】:

QWebView 不支持 QtWebchannel @eyllanesc,不知道,更新了问题 【参考方案1】:

WebView 默认不支持WebChannel。所以解决方案是使用WebSocketServerQWebChannelAbstractTransport,如下所示:

ma​​in.cpp

#include <QGuiApplication>
#include <QJsonDocument>
#include <QQmlApplicationEngine>
#include <QWebChannelAbstractTransport>
#include <QtWebView>

class WebSocketTransport : public QWebChannelAbstractTransport
    Q_OBJECT
public:
    using QWebChannelAbstractTransport::QWebChannelAbstractTransport;
    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);
;

int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
    qmlRegisterType<WebSocketTransport>("com.eyllanesc.org", 1, 0, "WebSocketTransport");
    QtWebView::initialize();

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();


#include "main.moc"

ma​​in.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import QtWebSockets 1.1
import QtWebView 1.1
import QtWebChannel 1.0
import com.eyllanesc.org 1.0

Window 
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    WebView 
        url: "qrc:/index.html"
        anchors.fill: parent
    

    QtObject 
        id: someObject
        property string someProperty: "prop"
        WebChannel.id: "core"
        function receiveText(text)
            console.log("receiveText: ", text)
        
        signal sendText(string text)
    

    WebSocketTransport
        id: transport
    

    WebSocketServer 
        listen: true
        port: 12345
        onClientConnected: 
            if(webSocket.status === WebSocket.Open)
                channel.connectTo(transport)
                webSocket.onTextMessageReceived.connect(transport.textMessageReceive)
                transport.onMessageChanged.connect(webSocket.sendTextMessage)
            
        
    

    WebChannel 
        id: channel
        registeredObjects: [someObject]
    

    // testing
    Timer
        interval: 500
        running: true
        repeat: true
        onTriggered: someObject.sendText(Qt.formatTime(new Date(), "hh:mm:ss") + " from QML")
    

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script type="text/javascript">
            //BEGIN SETUP
            function output(message) 
                var output = document.getElementById("output");
                output.innerHTML = output.innerHTML + message + "\n";
            
            window.onload = function() 
                if (location.search != "")
                    var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]);
                else
                    var baseUrl = "ws://localhost:12345";

                output("Connecting to WebSocket server at " + baseUrl + ".");
                var socket = new WebSocket(baseUrl);

                socket.onclose = function() 
                    console.error("web channel closed");
                ;
                socket.onerror = function(error) 
                    console.error("web channel error: " + error);
                ;
                socket.onopen = function() 
                    output("WebSocket connected, setting up QWebChannel.");
                    new QWebChannel(socket, function(channel) 
                        // make core object accessible globally
                        window.core = channel.objects.core;
                        input.innerHTML = core.someProperty;
                        document.getElementById("send").onclick = function() 
                            var input = document.getElementById("input");

                            var text = input.value;
                            if (!text) 
                                return;
                            
                            output("Sent message: " + text );
                            input.value = "";
                            core.receiveText(text + " From HTML");
                        

                        core.sendText.connect(function(message) 
                            output("Received message-" + core.someProperty + " : " + message);
                        );

                        core.receiveText("Client connected, ready to send/receive messages!");
                        output("Connected to WebChannel, ready to send/receive messages!");
                    );
                
            
            //END SETUP
        </script>
        <style type="text/css">
            html 
                height: 100%;
                width: 100%;
            
            #input 
                width: 400px;
                margin: 0 10px 0 0;
            
            #send 
                width: 90px;
                margin: 0;
            
            #output 
                width: 500px;
                height: 300px;
            
        </style>
    </head>
    <body>
        <textarea id="output"></textarea><br />
        <input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" />
    </body>
</html>

完整的例子可以看下面link

【讨论】:

所以不可能用 QML 做所有事情,必须先完成一些 C++ 代码(传输实现),而这正是我所缺少的。非常感谢您的工作,这是一个很好的解释。

以上是关于QML 中基于 WebSockets 的 Qt WebView 和 WebChannel的主要内容,如果未能解决你的问题,请参考以下文章

如何在QML(QT Creator)中使用发光脉动效果制作元素动画?

Qt基于QmL操作Windows任务栏按钮WinExtras实现

QT开发(五十四)———QML组件

Qt5。在 QML 中嵌入 QWidget 对象

Qt基于Qml图像帧动画播放

Qt基于Qml弹出右侧弹窗演示