zoukankan      html  css  js  c++  java
  • 通过WebChannel/WebSockets与QML中的HTML交互

    来源:通过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_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

    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上获得的帮助 - 我根本无法进行一切工作。

  • 相关阅读:
    nyoj131 小数相加 循环小时转换分数
    STL 之priority_queue
    XML序列化
    Change the hightlight item color
    TreeView ListView ItemSource
    .NET 下的序列化与反序列化
    WPF: WebBrowser TO Bitmap
    隐藏/显示 Office 标题栏 工具栏 winform webBrowser
    WPF全屏幕窗口
    .Net 注册表操作
  • 原文地址:https://www.cnblogs.com/suRimn/p/10238720.html
Copyright © 2011-2022 走看看