本文资源下载

https://www.alipan.com/s/7hHkLSzHxrz

Qt Web混合编程之CEF、QCefView简单介绍

web组件

Web组件是一种用于构建Web应用程序的独立、可重用的元素。它们通常包含了HTML、CSS和JavaScript代码,用于实现特定的功能或展示特定的内容。Web组件有助于将Web应用程序拆分成更小的、可维护的部分,提高了代码的可读性、可维护性和复用性。

目前来说,绝大多数web技术都是基于chrome,例如CEF、QCefView以及QWebEngineView等,这些都是在native界面里用来显示html网页,并且可以与web交互,例如常见的登录窗口,优酷的视频区域、WPS的稻壳商城等,这些都是用web技术实现的,而native端,只需要把网页展示出来,实现交互即可。

常见特点:

  • 独立性:Web组件是独立的元素,可以在不同的页面或应用程序中重复使用,从而减少重复编写代码的工作量。

  • 封装性:Web组件将HTML、CSS和JavaScript封装在一起,形成一个独立的单元,有助于减少代码之间的耦合性。

  • 可定制性:Web组件通常提供了一些接口或属性,允许开发者根据需要进行定制和配置,以适应不同的使用场景。

  • 互动性:Web组件可以包含交互式的元素和功能,使用户能够与页面进行交互,提升用户体验。

CEF (Chromium Embedded Framework)

cef是一个开源项目,提供了一个基于Chromium内核的嵌入式浏览器框架,可以让开发者在自己的应用程序中集成浏览器功能。 CEF提供了一种简单的方式来展示网页内容,并且支持JavaScript、HTML5、CSS、SVG等现代Web技术。CEF提供了许多方便的API,使开发者可以轻松地实现浏览器功能,比如加载网页、JavaScript 执行、Cookie 管理等。

QCefView

QCefView 是一个基于 CEF 的 Qt 框架的封装库,可以让开发者在Qt应用程序中集成Chromium 内核的浏览器功能。QCefView提供了一些方便的接口和工具,使得在Qt程序中嵌入浏览器变得更加简单。 在使用QCefView时,开发者可以利用Qt的强大功能和CEF的浏览器引擎,快速地开发出具有浏览器功能的应用程序,比如内置浏览器、在线帮助文档、网页编辑器等。通过使用QCefView,开发者可以更加轻松地实现复杂的浏览器功能,同时保持Qt应用程序的整体风格和一致性。


#include <QApplication>

#include <QCefContext.h>
#include <QCefSetting.h>
#include <QCefView.h>

int
main(int argc, char* argv[])
{
    // create QApplication instance
    QApplication a(argc, argv);

    // build QCefConfig
    QCefConfig config;
    // set user agent
    config.setUserAgent("QCefViewTest");
    // set log level
    config.setLogLevel(QCefConfig::LOGSEVERITY_DEFAULT);
    // set JSBridge object name (default value is QCefViewClient)
    config.setBridgeObjectName("CallBridge");
    // port for remote debugging (default is 0 and means to disable remote debugging)
    config.setRemoteDebuggingPort(9000);
    // set background color for all browsers
    // (QCefSetting.setBackgroundColor will overwrite this value for specified browser instance)
    config.setBackgroundColor(Qt::lightGray);

    // WindowlessRenderingEnabled is set to true by default,
    // set to false to disable the OSR mode
    config.setWindowlessRenderingEnabled(true);

    // add command line args, you can any cef supported switches or parameters
    config.addCommandLineSwitch("use-mock-keychain");
    // config.addCommandLineSwitch("disable-gpu");
    // config.addCommandLineSwitch("enable-media-stream");
    // config.addCommandLineSwitch("allow-file-access-from-files");
    // config.addCommandLineSwitch("disable-spell-checking");
    // config.addCommandLineSwitch("disable-site-isolation-trials");
    // config.addCommandLineSwitch("enable-aggressive-domstorage-flushing");
    config.addCommandLineSwitchWithValue("renderer-process-limit", "1");
    // config.addCommandLineSwitchWithValue("disable-features", "BlinkGenPropertyTrees,TranslateUI,site-per-process");


//  // create QCefContext instance with config,
//  // the lifecycle of cefContext must be the same as QApplication instance
    QCefContext cefContext(&a, argc, argv, &config);

    auto settings = new QCefSetting;
    settings->setDefaultEncoding("UTF-8");
    settings->setBackgroundColor(Qt::white);
    settings->setWindowlessFrameRate(120);
    settings->setJavascript(true);
    settings->setImageLoading(true);
    settings->setWebGL(true);

    QCefView view("C:/Users/Administrator/Desktop/test/line-stack.html", settings);

  //  QCefView view("www.testufo.com", settings);
    view.show();

    return a.exec();
}

编译QCefView

  1. 下载 QCefView

QCefView官网     https://cefview.github.io/QCefView/
Github     https://github.com/CefView/QCefVie
  1. 克隆代码

git clone https://github.com/CefView/QCefView.git
git clone https://github.com/CefView/CefViewCore.git
  1. 虽然QCefView_工程里有CefViewCore目录,但是是空的,需要手动clone下CefViewCore的代码,然后放到QCefView/CefViewCore的文件夹里面

  2. 在根目录下创建sdk_x64文件夹,并使用clion 下使用vs2022 识别项目,注意识别过程会自动下载cef的包

-G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./../sdk_x64 -DBUILD_DEMO=ON
  1. build and install 即可

使用QCefView

初始化QCefContex

使用QCefView的第一步必须初始化一个QCefContext的实例,跟QApplication一样, 在应用程序的生命周期内必须有且仅有一个QCefContext实例

 QApplication a(argc, argv);

  // build QCefConfig
  QCefConfig config;
  config.setUserAgent("QCefViewTest");
  config.setLogLevel(QCefConfig::LOGSEVERITY_DEFAULT);
  config.setBridgeObjectName("CallBridge");
  config.setRemoteDebuggingPort(9000);
  config.setBackgroundColor(Qt::lightGray);

  // WindowlessRenderingEnabled is set to true by default, set to false to disable the OSR mode
  // config.setWindowlessRenderingEnabled(false);
  
  // add command line args
  config.addCommandLineSwitch("use-mock-keychain");

  // initialize QCefContext instance with config
  QCefContext cefContext(&a, argc, argv, &config);

  MainWindow w;
  w.show();
  return a.exec();

看到初始化QCefContext实例的时候需要传入一个QCefConfig

你可以通过QCefConfig设置一些CEF的配置参数,例如可以设置日志级别,调试端口等

不要试图主动析构QCefContext实列,该实例跟随Application的生命周期存在和销毁,如果提前销毁则会导致CEF内部状态错

创建QCefView实例

一旦初始化QCefContext完成,就可以创建QCefView对象了。

  // build settings for per QCefView
  QCefSetting setting;

#if CEF_VERSION_MAJOR < 100
  setting.setPlugins(false);
#endif

  setting.setWindowlessFrameRate(60);
  setting.setBackgroundColor(QColor::fromRgba(qRgba(255, 255, 220, 255)));
  // setting.setBackgroundColor(Qt::blue);

  // this site is test for input devices
  QDir dir = QCoreApplication::applicationDirPath();
  QString webResourceDir =  QString("file://")+ QDir::toNativeSeparators(dir.filePath("webres/index.html"));
  m_pLeftCefViewWidget = new CefViewWidget(webResourceDir, &setting);

  m_ui.leftCefViewContainer->layout()->addWidget(m_pLeftCefViewWidget);

  // allow show context menu for both OSR and NCW mode
  m_pLeftCefViewWidget->setContextMenuPolicy(Qt::DefaultContextMenu);

  // connect the invokeMethod to the slot
  connect(m_pLeftCefViewWidget, &QCefView::invokeMethod, this, &MainWindow::onInvokeMethod);

Qt与JS通信

QCefView提供C++/Javascript互操作的能力,因此开发者可以从C++中调用Javascript代码,反之亦然

JS发送信息给Qt

js调用:invokeMethod,Qt Signals处
  • JS 发出

Javascript方法invokeMethod(name, ...args)是 异步操作,这意味着该方法的调用会立即返回,无论对应的C++ Qt slot是否已经执行完

     function onInvokeMethodClicked(name, ...arg) {
        CallBridge.invokeMethod(name, ...arg);
      } 


      function testInvokeMethod() {
        let d = {
          d1: true,
          d2: 5678,
          d3: "test object",
          d4: [1, "2", false],
          d5: {
            d1: true,
            d2: 5678,
            d3: "nested object",
            d4: [1, "2", true],
          },
        };
        onInvokeMethodClicked("TestMethod", 1, false, "arg3", d);
      } 

     <label> Test Case for InvokeMethod </label>
      <br />
      <input type="button" value="Invoke Method" onclick="testInvokeMethod()" />
      <br />
      <br />
  • Qt 接收

connect(m_pLeftCefViewWidget, &QCefView::invokeMethod, this, &MainWindow::onInvokeMethod)
 **const QString& method, method就是js调用invokeMethod函数的第一个参数。通过method我们可以进行消息过滤,在Qt中进行消息分发处理,
 **const QVariantList& arguments,通过变量类型我们可知,这个参数同时可以传递多种 
 ** 不同类型的数据到Qt中来,我们可以通过解析消息体,做具体的业务处理。
void MainWindow::onInvokeMethod(int browserId, int64_t frameId, const QString& method, const QVariantList& arguments)
{
  // extract the arguments and dispatch the invocation to corresponding handler
  if (0 == method.compare("TestMethod")) {
    QString title("QCef InvokeMethod Notify");
    QString text = QString("================== Current Thread: QT_UI ==================\r\n"
                           "Frame: %1\r\n"
                           "Method: %2\r\n"
                           "Arguments:\r\n")
                     .arg(frameId)
                     .arg(method);

    for (int i = 0; i < arguments.size(); i++) {
      auto jv = QJsonValue::fromVariant(arguments[i]);

      // clang-format off
          text.append(
              QString("%1 Type:%2, Value:%3\r\n")
              .arg(i).arg(arguments[i].typeName()).arg(arguments[i].toString())
          );
      // clang-format on
    }

    auto jsonValue = QJsonDocument::fromVariant(arguments);
    auto jsonString = QString(jsonValue.toJson());
    text.append(QString("\r\nArguments List in JSON format:\r\n%1").arg(jsonString));

    QMessageBox::information(this->window(), title, text);
  } else {
  }
}

JS调用CefViewQuer
  • JS 发出

window.CefViewQuery(query)是另一种从Javascript中向C++中通信的机制,这种方式的通信是异步操

function onCallBridgeQueryClicked() {
        var query = {
          request: document.getElementById("message").value,
          onSuccess: function (response) {
            alert(response);
          },
          onFailure: function (error_code, error_message) {
            alert(error_message);
          },
        };
        window.CefViewQuery(query);
      }

      <label> Test Case for QCefQuery </label>
      <br />
      <textarea id="message" style="width: 320px; height: 120px">
this message will be processed by native code.</textarea>
      <br />
      <input type="button" value="Query" onclick="onCallBridgeQueryClicked()" />
      <br />
      <br />
  • Qt接收

当从Javascript中调用CefViewQuery方法时,以下Qt signal会被触发:

public void cefQueryRequest(int browserId,int frameId,const QCefQuery & query)

connect(m_pLeftCefViewWidget, &QCefView::cefQueryRequest, this, &MainWindow::onQCefQueryRequest);

void MainWindow::onQCefQueryRequest(int browserId, int64_t frameId, const QCefQuery& query)
{
  QString title("QCef Query Request");
  QString text = QString("Current Thread: QT_UI\r\n"
                         "Query: %1")
                   .arg(query.request());

  QMessageBox::information(this->window(), title, text);

  QString response = query.request().toUpper();
  query.setResponseResult(true, response);
  m_pLeftCefViewWidget->responseQCefQuery(query); //qt对js发送回应
}

Qt发送信息给Js

  • Qt 发送

以注册消息事件的方式将消息从Qt发送到JS

  1. public bool triggerEvent(const QCefEvent & event

  2. public bool triggerEvent(const QCefEvent & event,int frameId)

  3. public bool broadcastEvent(const QCefEvent & event)

以上三个方法的调用全部都是异步操

MainWindow::onBtnChangeColorClicked()
{
  if (m_pLeftCefViewWidget) {
    // create a random color
    QColor color(QRandomGenerator::global()->generate());

    // create the cef event and set the arguments
    QCefEvent event("colorChange");
    event.arguments().append(QVariant::fromValue(color.name(QColor::HexArgb)));

    // broadcast the event to all frames in all browsers created by this QCefView widget
    m_pLeftCefViewWidget->broadcastEvent(event);
  }
}
  • Js 接收

Javascript调用下列函数进行接收:

  1. addEventListener(name, listener

  2. removeEventListener(name, listener)

function onLoad() {
        if (typeof CallBridge == "undefined") {
          alert("Not in CefView context");
          return;
        }

        CallBridge.addEventListener("colorChange", function (color) {
          document.getElementById("main").style.backgroundColor = color;
        });
      }

<body onload="onLoad()" id="main" class="noselect">

QCefView 常用的函数

信号

  • void invokeMethod(int browserId, qint64 frameId, const QString& method, const QVariantList& arguments)

  • void cefQueryRequest(int browserId, qint64 frameId, const QCefQuery& query);

  • void loadingStateChanged(int browserId, bool isLoading, bool canGoBack, bool canGoForward);

  • void loadStart(int browserId, qint64 frameId, bool isMainFrame, int transitionType);void loadStart(int browserId, qint64 frameId, bool isMainFrame, int transitionType);

  • void loadEnd(int browserId, qint64 frameId, bool isMainFrame, int httpStatusCode);void loadEnd(int browserId, qint64 frameId, bool isMainFrame, int httpStatusCode);

  • void loadError(int browserId,qint64 frameId,bool isMainFrame,int errorCode,const QString& errorMsg,const QString& failedUrl);

函数

  • void addLocalFolderResource(const QString& path, const QString& url, int priority = 0)

  • void addArchiveResource(const QString& path, const QString& url, const QString& password = "", int priority = 0);

  • bool broadcastEvent(const QCefEvent& event);

QCefEvent 常用的函数

  • void setArguments(const QVariantList& args);

  • bool responseQCefQuery(const QCefQuery& query);//qt接收到js通过CefViewQuery发送数据后,做出的回应

QCefEvent 常用的函数

  • void setResponseResult(bool success, const QString& response, int error = 0) const

  • bool executeJavascriptWithResult(qint64 frameId, const QString& code, const QString& url, qint64 context);

void MainWindow::onBtnCallJSCodeClicked()
{
  int64_t context = 1000;
  QString code = "alert('hello QCefView'); return {k1: 'str', k2: true, k3: 100};";
  m_pLeftCefViewWidget->executeJavascriptWithResult(QCefView::MainFrameID, code, "", context);
}
connect(m_pLeftCefViewWidget, &QCefView::reportJavascriptResult, this, &MainWindow::onJavascriptResult);

void MainWindow::onJavascriptResult(int browserId, int64_t frameId, int64_t context, const QVariant& result)
{
  auto jsonValue = QJsonDocument::fromVariant(result);
  auto jsonString = QString(jsonValue.toJson());

  QString title("Javascript result notification");
  QString text = QString("Context id: %1\r\nResult in JSON format:\r\n%2").arg(context).arg(jsonString);

  QMessageBox::information(this->window(), title, text);
}


本站由 困困鱼 使用 Stellar 创建。