QT中使用QWebEngineView和QWebChannel与HTML+JS进行互操作

使用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)

 

posted @ 2020-12-21 15:57  小船1968  阅读(3409)  评论(0编辑  收藏  举报