在項目開發中,常常會有在原生應用程序中嵌入 HTML 頁面或者 Web 項目,並且需要應用程序與所加載的 HTML 頁面的相互通信的需求。

本篇文章基於 Qt 框架,講解如何使用 Qt WebChannel 實現 C++/QML 和 HTML 頁面之間交互,包括:

  • 在 HTML 頁面調用 C++/QML 對象中的函數(異步)
  • 向 HTML 中發送 QML/C++ 信號
  • 在 HTML 中調用 QML/C++ 對象的屬性
  • 在 HTML 中調用 QML/C++ 對象的枚舉類型和枚舉值

2011 年 10 月,諾姆·羅森塔爾(Noam Rosenthal)提出了這個方法,他稱之為 Qt WebChannel。他的想法既簡單又強大:通過利用 Qt 的自省(introspection,也叫內省)系統,他可以在 JavaScript 客戶端創建模擬對象,該對象可以反射服務端 QML/QObject 對象的 API。對於 QML 主機和 HTML/JavaScript 客戶端之間的通信方式,他選擇了 WebSockets,但是 WebChannel 提供的 API 是完全異步的

Qt WebChannel 支持服務端(QML/C++ 應用程序)和客戶端(HTML/JavaScript 或 QML 應用程序)之間的點對點(peer-to-peer)通信。它提供了一個 JavaScript 庫,用於將 C++ 和 QML 應用程序與 HTML/JavaScript 和 QML 客戶端無縫集成。客戶端必須使用 JavaScript 庫來訪問主機端應用程序發布的序列化的 QObjects 對象。

注:本文中提到的客戶端和服務端其實是在同一個應用程序內,因為 WebChannel 是基於 WebSocket 實現的,所以會有這種客戶端和服務端的叫法。

QML/HTML 混合應用程序

本節演示 QML 應用程序和 HTML 之間如何進行交互。

本應用用到了 WebChannel 和 WebEngine 兩個主要模塊,所以要將下面這行添加到 qmake .pro 文件中:

QT += webchannel webengine

QML 服務端

在 QML 服務端,首先導入 Qt WebChannel 模塊,以及 Qt WebEngine 模塊:

import QtWebChannel 1.0
import QtWebEngine 1.5

然后創建一個想要發布到 HTML/JavaScript 客戶端的對象:

QtObject {
    id: myObject

    // 注冊方法 1
    // 使用注冊方法 2 時不需要此行代碼
    WebChannel.id: "foo" //這個 id 可以在 html 中使用
    
    // 以下為 JavaScript 代碼可以訪問的信號、方法和屬性
    signal someSignal(string message);

    function someMethod(message) {
        console.log(message);
        someSignal(message);
        return "foobar";
    }

    property string hello: "world"
}

最后將該對象在 WebView 控件中發布到 HTML 客戶端中:

WebEngineView {
    anchors.fill: parent
    url: "file:///C:/test.html"
    
    webChannel: WebChannel {
        id: webChannel

        // 注冊方法 1
        registeredObjects: [myObject]
        // 注冊方法 2
        //Component.onCompleted: {
        //    // "foo" 是該對象在 JavaScript 端的調用標識
        //    webChannel.registerObject("foo", myObject)
        //}
    }
}

HTML / JavaScript 客戶端

在客戶端,首先,通過 Qt 資源 URL 包含客戶端 qwebchannel.js 庫(該文件可以在 %QtDir%\Src\qtwebchannel\examples\webchannel\shared\qwebchannel.js 中找到,經過測試在 Qt 5.12 以后不將該文件加入資源也可),並在 HTML 文件中插入一段 JavaScript:

<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>

然后,在 JavaScript 代碼中實例化 QWebChannel 對象並設置回調函數。當 web 通道的初始化成功時,將調用回調函數。此外,您可以傳遞 qt.webChannelTransport 對象到 channel 中,詳見下文:

new QWebChannel(qt.webChannelTransport, function(channel) {
    // 所有通過 WebChannel 發布的對象都可以在 channel.objects 中訪問的到
    window.foo = channel.objects.foo;
 
    // 訪問一個屬性
    alert(foo.hello);
    
    // Writing a property will instantly update the client side cache.
    // The remote end will be notified about the change asynchronously
    foo.hello = "Hello World!";
 
    // 連接信號
    foo.someSignal.connect(function(message) {
        alert("Got signal: " + message);
    });
 
    // 調用方法,並*異步*接收返回值
    foo.someMethod("bar", function(ret) {
        alert("Got return value: " + ret);
    });
    
    // One can also access enums that are marked with Q_ENUM:
    //console.log(foo.MyEnum.MyEnumerator);
});

C++/QML/HTML 混合應用程序

利用在 QML 應用程序中可以引用 C++ 類的這個特點,我們也可以實現在 HTML 頁面中調用 C++ 類的需求。

首先定義 C++ 對象並將它注冊到 QML 中,我們在 main.cpp 中添加下面的一行,注意, TestObejct 類必須直接繼承自 Object:

qmlRegisterType<TestObejct>("TestObejct", 1, 0, "TestObejct");

然后在 QML 文件中將這個類對象注冊到 WebChannel,你可以使用上節中提到的方法直接調用 C++ 類中已存在的信號、方法、屬性和枚舉類型,也可以在 QML 中繼續擴展其他方法:

import TestObejct 1.0
...
TestObejct {
    id: myObject
    WebChannel.id: "foo"
    
    // 在 QML中可以繼續擴展信號、方法和屬性
    signal someSignal2(string message);

    function someMethod2(message) {
        console.log(message);
        someSignal2(message);
        return "foobar";
    }

    property string hello2: "world"
}

Qt WebChannel 不只可以在 QML 應用程序中使用,在純 Qt/C++ 應用程序中也可以創建一個 QWebChannel 並發布 QObject 實例。

 參考:https://www.pressc.cn/1085.html

---------------------------------------------------------------------------------------------------------------------------------------------------