Qt和JavaScript使用QWebChannel交互一——和Qt內嵌網頁交互

Qt和JavaScript使用QWebChannel交互一——和Qt內嵌網頁交互


前言

    Qt提供了QWebChannel來和網頁進行通訊,只需要註冊自定義對象一下,就可以直接綁定訊號槽來進行Qt程式和網頁之前的通訊,非常方便
    下面使用一個案例來學習QWebChannel
    環境:vs2017+Qt5.11.2 寫Qt程式碼,vsCode寫js程式碼


一、效果

直接上圖

請添加圖片描述

二、實現過程

1. Qt端

  1. 新建一個Qt Gui項目,取名WebChannelDemo
    在這裡插入圖片描述
  2. 一路下一步,最後選擇QWidget基類
    在這裡插入圖片描述
  3. 得到一個這樣結構的項目
    在這裡插入圖片描述
  4. 使用Qt Designer打開webchanneldemo.ui,拖一個介面
    在這裡插入圖片描述
  5. 這個時候再來創建一個用於通過WebChannel通訊的WebTransport類,基類選擇QObject
    在這裡插入圖片描述
    在這裡插入圖片描述
  6. 為了方便,不用每次使用都創建一個WebTransport對象,我們將這個類寫成單例類,然後定義和js交互的訊號的槽函數,最近定義一個宏來使用實例
#pragma once

#include <QObject>

class WebTransport : public QObject
{
	Q_OBJECT

	WebTransport(QObject *parent = nullptr);
	~WebTransport();
public:
	// 獲取實例
	static WebTransport* instance();
signals:
	// 向js發送訊號
	void msgToJs(const QString& msg);
	// 將從js接收的數據發送出去
	void receviedJsMsg(const QString& msg);
public slots:
	// js調用此函數,接收js傳入的數據
	void msgToQt(const QString& msg);
};
// 定義一個宏
#ifndef WEB_TRSPT
#define WEB_TRSPT WebTransport::instance()
#endif // !WEB_TRSPT
  1. 最後,我們到WebChannelDemo類中來初始化一下,主要做了以下幾件事:
    1. 關聯訊號槽
    2. 實例化一個QWebChannel對象
    3. WebTransport單例對象註冊到QWebChannel
    4. QWebChannel對象設置到網頁中去
    5. 最後再載入本地網頁
void WebChannelDemo::setup()
{
	// 綁定訊號槽
	connect(ui.pushButton, &QPushButton::clicked, [this]() {
		ui.plainTextEdit->appendPlainText(QStringLiteral("發送消息到js:") + ui.lineEdit->text());
		emit WEB_TRSPT->msgToJs(ui.lineEdit->text());
	});
	connect(WEB_TRSPT, &WebTransport::receviedJsMsg, [this](const QString& msg) {
		ui.plainTextEdit->appendPlainText(QStringLiteral("接收js資訊:") + msg);
	});

	// 構造一個channel對象
	QWebChannel* channel = new QWebChannel(this);
	// 向channel對象註冊自定義對象
	channel->registerObject(QStringLiteral("webBridge"), WEB_TRSPT);
	// 使用webview的page設置channel對象
	ui.webEngineView->page()->setWebChannel(channel);
	// 最後載入網頁
    ui.webEngineView->load(qApp->applicationDirPath()+"/channel/index.html");
}

2. 網頁端

  1. 先創建一個channel目錄,放在項目生成目錄,和生成的可執行文件同級
    在這裡插入圖片描述
  2. 使用vsCode打開這個目錄,並創建如下項目結構
    在這裡插入圖片描述
  3. 打開index.html文件,先編寫一個這樣的介面
    在這裡插入圖片描述
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <!-- 樣式 -->
    <link rel="stylesheet" href="./css/style.css">
    <!-- js -->
    <script src="./js/qwebchannel.js"></script>
    <script src="./js/main.js"></script>
</head>
<body>
    <p>我是網頁</p>
    <textarea name="textArea" id="textArea"></textarea><br>
    <input type="text" id="lineEdit"><br>
    <button id="sendBtn" onclick="senBtnClicked();">發送</button>
    <button id="clearBtn" onclick="clearBtnClicked();">清空</button>
</body>
</html>
  1. 打開main.js文件,編寫js程式碼,重點在初始化函數中,做了以下幾件事
    1. 直接構造一個QWebChannel對象,用回調函數函數接收創建好的QWebChannel對象
    2. 使用QWebChannel對象獲取Qt端註冊的對象
    3. 給註冊對象的訊號綁定一個回調函數(槽函數),當Qt端註冊的對象此訊號發送時,回調函數被調用
    4. 調用註冊對象的槽函數,給Qt端發送消息,直接使用Qt端註冊對象的槽函數名來調用
// 顯示資訊
function outputMsg(msg) {
    var textArea = document.getElementById("textArea");
    if (textArea) {
        textArea.innerHTML = textArea.innerHTML + msg + "\n";
        textArea.scrollTop = textArea.scrollHeight;
    }
}
// 發送按鈕點擊
function senBtnClicked() {
    // 獲取輸出標籤
    var lineEdit = document.getElementById('lineEdit');
    // 調用Qt對象函數
    webTransport.msgToQt(lineEdit.value);
    // 顯示
    outputMsg("發送資訊到Qt:" + lineEdit.value);
}
// 清空
function clearBtnClicked() {
    document.getElementById("textArea").innerHTML = "";
}
// 初始化
window.onload = function init() {
    if (typeof qt !== "undefined") {
        new QWebChannel(qt.webChannelTransport, function(channel) {
            // 獲取Qt註冊的對象,設置到主窗口(這裡的webTransport就是Qt端註冊時的字元串id)
            window.webTransport = channel.objects.webTransport;
            // 綁定註冊對象的訊號
            webTransport.msgToJs.connect(function(msg) {
                outputMsg("接收Qt資訊:" + msg);
            });
            webTransport.msgToQt("初始化channel成功!");
        });
    } else {
        alert("初始化qwebchannel失敗")
    }
}

三、過程中出現的問題

問題一

  1. 問題描述:運行時,Qt向Js端發送消息沒有問題,Js端向Qt端發送消息時失敗,會報如下錯誤
Cannot invoke unknown method of index -1 on object webTransport(0x...)
  1. 問題原因及解決辦法:
    使用Qt 5.11.2編譯生成的可執行程式,而網頁端用的是Qt 5.14qwebchannel.js文件,版本不兼容導致的,換成對應的qwebchannel.js文件就好了

問題二

  • 在載入本地網頁時,為什麼是先設置QWebChannel再載入網頁?
  • 因為最開始的想法是,如果直接先使用ui.webEngineView->page()獲取QWebEnginePage對象,應該獲取到的是個nullptr,這個時候直接設置QWebChannel,程式會崩掉,那我先載入網頁,然後QWebEngineView會內部構造一個QWebEnginePage對象,我再通過page()函數獲取,這個時候肯定不是nullptr了,再去設置QWebChannel,這個過程簡直完美,但是出人意料的是,按照這個過程設置的QWebChannel並沒有生效,也不能和js交互
  • 解決辦法就是先設置QWebChannel再載入網頁,通過查看Qt 源碼發現,通過page()函數獲取QWebEnginePage對象時,內部做出了判斷,如果內部維護的QWebEnginePage對象為空時,會直接構造一個QWebEnginePage對象,並不會返回空指針,設置完QWebChannel後再去調用load()函數載入網頁時,內部會直接拿到設置好QWebChannelQWebEnginePage對象去載入網頁
QWebEnginePage* QWebEngineView::page() const
{
		Q_D(const QWebEngineView);
		if (!d->page) {
 	    QWebEngineView *that = const_cast<QWebEngineView*>(this);
		 that->setPage(new QWebEnginePage(that));
		 }
		return d->page;
}

void QWebEngineView::load(const QUrl& url)
{
	page()->load(url);
}

四、項目完整源碼

//gitee.com/doyoung126/qt_-demo.git

下一篇:Qt和JavaScript使用QWebChannel交互二——和瀏覽器打開的網頁交互(還沒寫,抽時間寫)

五、總結

總結:遇到凡事不要慌,先掏出手機拍個朋網友圈
在這裡插入圖片描述