使用WebEngineView與WebChannel,實現QT與html數據傳輸和事件響應。
1. 准備工作
1.1 項目配置
(1)使用QMake時,在pro文件中加入
QT += webchannel webengine
(2)使用CMake時,在CMakeList.txt中加入
find_package(Qt5 COMPONENTS Widgets WebEngineWidgets WebChannel REQUIRED)
target_link_libraries(webEngineTest PRIVATE Qt5::Widgets Qt5::WebEngineWidgets Qt5::WebChannel)
注意在QMake中不需要大小寫區分,而CMake時就需要將大小寫分開。
1.2 加入webEngineView和webChannel
在MainWindow.h中加入兩個變量
QWebEngineView *webView = nullptr;
QWebChannel *webChannel = nullptr;
在MainWindow.cpp的相關函數中(可以是MainWindow的構造函數,也可以是菜單響應函數中)加入webEngineView
webView = new QWebEngineView(this); QString fpath = QCoreApplication::applicationDirPath(); webView->load( QUrl("file:///" + fpath + "/test.html")); webView->show();
加入webChannel
webChannel = new QWebChannel; webView->page()->setWebChannel(webChannel);
一定要注意:必須將webChannel設置為webEngineVIew的webChannel,才能通過webChannel與網頁進行通信。
2. 准備交互的QT類
與html交互的主要工作需要一個QT類實現,這個類需要通過webChannel進行注冊才能由js訪問
下面是可以由js訪問的WebClass類
#ifndef WEBCLASS_H #define WEBCLASS_H #include <QObject> #include <QMessageBox> class WebClass : public QObject { Q_OBJECT //Q_PROPERTY(QString content MEMBER m_content) Q_PROPERTY(QString content MEMBER m_content NOTIFY contentChanged) //該屬性可由頁面訪問 public: WebClass(){}; QString getContent(){return m_content;} signals: void contentChanged(QString nc); public slots: void jscallme(const QString &text) //該函數是頁面端調用的 { QMessageBox::information(NULL, "jscallme", text); } void jscallme() //該函數是頁面端調用的 { QMessageBox::information(NULL, "jscallme", m_content); } private: QString m_content; }; #endif // WEBCLASS_H
它有幾個約束:
(1)必須由QObject繼承,並且添加了Q_OBJECT宏
(2)必須將JS可訪問的函數設置為public slots
(3)如果JS需要訪問其成員變量,除定義該變量(QString m_content;)外需要用Q_PROPERTY宏
Q_PROPERTY(QString content MEMBER m_content NOTIFY contentChanged) //該屬性可由頁面訪問
其中content 為JS訪問的變量名,m_content為本類的變量名;
NOTIFY contentChanged可以缺省,它表示當在C++變量發生變化時的發出的消息,該消息可由JS響應。
為了發送該消息,我們必須在WebClass類中聲明一個信號函數,即
signals: void contentChanged(QString nc);
這個函數只能聲明不能實現(它會由MOC編譯實現),而響應函數由JS中指定:
webobj.contentChanged.connect(updateattribute);
這里webobj是WebClass注冊后,在頁面中的一個實例,contentChanged則是WebClass中的contentChanged信號函數,這一行將contentChanged信號與響應函數updateattribute進行關聯,從而一旦C++對象中m_content成員的值由webobject->setProperty("content", v)函數進行修改(必須使用此函數,其他函數修改了不會引起信號)時,JS就會響應,它可以更新頁面中相關元素的值。
3. 將交互類作為成員加入主類中
(1)加入類成員。我們這里的主類為MainWindow類
WebClass *webobj = nullptr;
(2)注冊該成員
在主類的相應位置,注冊該成員變量。我們在主類的構造函數中加入:
webChannel = new QWebChannel; webobj = new WebClass(); webChannel->registerObject("webobj", webobj); webView->page()->setWebChannel(webChannel);
注意,這里是在webEngineView設置webChannel之前完成了注冊交互實體的工作。其中 webChannel->registerObject("webobj", webobj); 函數就是注冊函數,"webobj"是JS訪問該對象時的對象名, 后面的webobj則是C++對象實例的指針。通過WebChannel的注冊工作,WebChannel就知道了該類的結構,並代理JS完成函數調用和成員訪問。
4. 准備HTML頁面
該頁面中包含有一個特殊的JS文件,它是Qt目錄"C:\Qt\Qt5.12.1\Examples\Qt-5.12.1\webchannel\shared"
下的qwebchannel.js
文件,要使用QWebChannel,必須引用該文件。因為QWebChannel
是由該文件定義,所以必須首先加載該文件。我們將該文件拷貝到html
目錄下(當然也可以是需要的其他目錄),然后編輯例子文件:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <script src="qwebchannel.js"></script> <script type="text/javascript"> var webChannel = new QWebChannel(qt.webChannelTransport, //這里的webChannel是全局的變量,可以在其它位置訪問 function(channel){ var webobj = channel.objects.webobj; window.foo = webobj; //將此webobj賦給了window.foo,則可以在其他函數中訪問該對象(其中foo是任意合法名稱,表示給window增加了一個成員) webobj.content = 'sdfef中文'; webobj.jscallme(); document.getElementById("ctext").innerHTML = webobj.content; webobj.contentChanged.connect(updateattribute); }); var updateattribute=function(text) { //document.write(text); //var webobj = webChannel.objects.webobj; //訪問全局變量webChannel alert(window.foo.content); //這里可以訪問全局的window.foo,它就是我們注冊的webobj document.getElementById("mytext").innerHTML = text; //alert(webobj.content); } </script> <p id="mytext">This is my first html</p> <p id="ctext"></p> </body> </html>
(1)頁面文件首先包含了qwebchannel.js,它是使用WebChannel的基礎;
(2)頁面創建了一個QWebChannel實例,作為全局變量,這樣別的JS代碼也可以訪問它了;
(3)QWebChannel實例的構造函數有兩個參數,第一個不清楚,且保持原樣。第二個參數是一個回調函數(姑且這樣稱),該函數在創建時調用,函數的參數就是本次創建的QWebChannel實例;
(4)在var webobj = channel.objects.webobj;一行中,channel.objects.webobj是我們在QT主類中注冊的webobj對象實例,我們將它賦值給了一個變量,以便於引用;
(5)下面一行window.foo = webobj,則表示為全局的window實例增加一個成員foo(這個名字可以自己定),它是webobj的別名,這樣我們可以在別的地方訪問webobj;
(6)下面三行是對注冊實例webobj的訪問
webobj.content = 'sdfef中文'; //訪問在QT中定義為content的成員變量,其在QT中的成員是m_content; webobj.jscallme(); //調用該對象的public slots函數 document.getElementById("ctext").innerHTML = webobj.content; //使用該對象的成員為html元素賦值
(7)最后一行最為關鍵,它將QT的信號與JS的響應函數關聯,從而響應QT發出的信號
webobj.contentChanged.connect(updateattribute);
(8)其中updateattribute是JS定義的響應函數名稱,下面是它的定義:
var updateattribute=function(text) { //document.write(text); //var webobj = webChannel.objects.webobj; //訪問全局變量webChannel alert(window.foo.content); //這里可以訪問全局的window.foo,它就是我們注冊的webobj document.getElementById("mytext").innerHTML = text; }
需要注意的是,這個響應函數應當與QT中C++的信號函數在參數上保持一致。
可以看出,這里可以訪問全局成員window.foo
5. 程序全部代碼
5.1 main.cpp
//main.cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
5.2 mainwindow類
//mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtWebEngineWidgets/QWebEngineView> #include "webclass.h" #include <QtWebChannel/QWebChannel> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_pushButton_clicked(); private: Ui::MainWindow *ui; QWebEngineView *webView = nullptr; QWebChannel *webChannel = nullptr; WebClass *webobj = nullptr; }; #endif // MAINWINDOW_H
//mainwindow.cpp #include "mainwindow.h" #include "./ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); webView = new QWebEngineView(this); QString fpath = QCoreApplication::applicationDirPath(); webView->load( QUrl("file:///" + fpath + "/test.html")); webView->show(); webView->move(0, 60); webView->resize(this->size().width(), this->size().height() - 60); //mainLayout->addWidget(webView); webChannel = new QWebChannel; webobj = new WebClass(); webChannel->registerObject("webobj", webobj); webView->page()->setWebChannel(webChannel); } MainWindow::~MainWindow() { delete ui; } int times = 1; void MainWindow::on_pushButton_clicked() { QString t = QString::number(times++); QString text = "1234567"; text.append(t); QVariant v(text); qDebug() << v.typeName() << endl; webobj->setProperty("content", v); qDebug() << webobj->getContent() << endl; }
本類中,on_pushButton_clicked是mainWindow中加入的一個pushbutton的響應函數,這個函數調用webobj->setProperty("content", v);來改變名為content的成員變量的值,並發送contentChanged信號,由頁面響應。這里"content"是m_content在JS中的名稱,本函數實際上是調用了JS來改變對象webobj的成員變量值,然后觸發相關事件響應。
5.3 webclass類(交互注冊類)
//webclass.h #ifndef WEBCLASS_H #define WEBCLASS_H #include <QObject> #include <QMessageBox> class WebClass : public QObject { Q_OBJECT //Q_PROPERTY(QString content MEMBER m_content) Q_PROPERTY(QString content MEMBER m_content NOTIFY contentChanged) //該屬性可由頁面訪問 public: WebClass(); QString getContent(){return m_content;} signals: void contentChanged(QString nc); public slots: void jscallme(const QString &text) //該函數是頁面端調用的 { QMessageBox::information(NULL, "jscallme", text); } void jscallme() //該函數是頁面端調用的 { QMessageBox::information(NULL, "jscallme", m_content); } private: QString m_content; }; #endif // WEBCLASS_H
//webclass.cpp #include "webclass.h" WebClass::WebClass() { }
WebClass有兩個slot可供JS調用,其中一個是帶參的。這個參數前面的const是不能去掉的,否則會產生錯誤:Could not convert argument QJsonValue(string, “sd”) to target type . 可見,WebChannel的通信是通過JSON進行的。
5.4 HTML頁面
見4.
5.5 CMakeList.txt
cmake_minimum_required(VERSION 3.5) project(ScenarioBuilder LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_PREFIX_PATH $ENV{QT5_14_0_vs2017_64}) # QtCreator supports the following variables for Android, which are identical to qmake Android variables. # Check http://doc.qt.io/qt-5/deployment-android.html for more information. # They need to be set before the find_package(Qt5 ...) call. #if(ANDROID) # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") # if (ANDROID_ABI STREQUAL "armeabi-v7a") # set(ANDROID_EXTRA_LIBS # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) # endif() #endif() find_package(Qt5 COMPONENTS Widgets WebEngineWidgets WebChannel REQUIRED) if(ANDROID) add_library(ScenarioBuilder SHARED main.cpp mainwindow.cpp mainwindow.h mainwindow.ui interactor.h interactor.cpp mywebview.cpp mywebview.h ) else() add_executable(ScenarioBuilder main.cpp mainwindow.cpp mainwindow.h mainwindow.ui interactor.h interactor.cpp mywebview.cpp mywebview.h ) endif() target_link_libraries(ScenarioBuilder PRIVATE Qt5::Widgets Qt5::WebEngineWidgets Qt5::WebChannel)