Qt网络篇:QCefView入门示例程序简介

本文介绍了如何在Qt应用中使用QCefView控件,展示了如何创建和展示网页,以及如何实现Qt界面与网页的双向消息传递。通过创建Qt项目,设置QCefContext和QCefView实例,以及解析源码,详细阐述了加载Web资源的方法和C++与Javascript的互操作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  接上一篇文章QCefView入门及环境配置之后,我们已经了解了QCefView的基本编译以及环境的配置,这一节我们依据管网案例来实现QCefView的入门功能,具体包括:

  • 在Qt控件中嵌入一个网页并正常显示
  • 通过网页发送消息给Qt界面
  • 通过Qt界面按钮来发送消息给网页

主要参考官网的说明以及源码中的QCefViewTest项目代码。

一、效果展示

在这里插入图片描述

  • 新建cef窗口,其中*.html文件自己简单写的。
  • 点击“修改颜色”可以修改html网页部件颜色。
  • 点击“Invoke Method”发送消息到Qt桌面端。
  • 点击“Query”可以将网页控件中的一段消息发送到Qt桌面端,并返回消息到web端。

二、创建Qt项目及UI

  我使用的是VS创建Qt项目方式,本来想用QtCreator,但后期使用过程中总出现*.lib文件找不到问题,具体原因也没有查到。但用VS打开Qt项目编译又正常,因此索性直接用VS创建项目实现代码功能吧。
在这里插入图片描述
在这里插入图片描述

三、源码

  1、初始化QCefContext实例:使用QCefView的第一步必须初始化一个QCefContext的实例,跟QApplication一样, 在应用程序的生命周期内必须有且仅有一个QCefContext实例;可以看到初始化QCefContext实例的时候需要传入一个QCefConfig。你可以通过QCefConfig设置一些CEF的配置参数,例如可以设置日志级别,调试端口等。更多详细参数请参考API文档QCefConfig。不要试图主动析构QCefContext实列,该实例跟随Application的生命周期存在和销毁,如果提前销毁则会导致CEF内部状态错误。
main.cpp

#include <QApplication>

#include <QCefContext.h>

#include "MainWindow.h"

int
main(int argc, char* argv[])
{
  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);

  // add command line args
  // config.addCommandLineSwitch("allow-universal-access-from-files");
  config.addCommandLineSwitch("enable-media-stream");
  config.addCommandLineSwitch("use-mock-keychain");
  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");

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

  MainWindow w;
  w.show();

  return a.exec();
}

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

#ifndef QCEFVIEWTEST_H
#define QCEFVIEWTEST_H

#include <QMainWindow>

#include "ui_MainWindow.h"

#include "CefViewWidget.h"

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  MainWindow(QWidget* parent = 0);
  ~MainWindow();

protected:
  void createCefView();

  // QCefView slots
protected slots:
  void onInvokeMethod(int browserId, int64_t frameId, const QString& method, const QVariantList& arguments);

  void onQCefQueryRequest(int browserId, int64_t frameId, const QCefQuery& query);
  
  // ui slots
protected slots:
    void onBtnNewBrowserClicked();
    void onBtnChangeColorClicked();
private:
  Ui::MainWindow ui;

  CefViewWidget* cefViewWidget = nullptr;
  QRegion draggableRegion_;
  QRegion nonDraggableRegion_;
};

#endif // QCEFVIEWTEST_H
#include "MainWindow.h"

#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QJsonDocument>
#include <QJsonValue>
#include <QMessageBox>
#include <QRandomGenerator>
#include <QTextCodec>
#include <QCefContext.h>

#pragma execution_character_set("utf-8")

#define URL_ROOT "http://QCefViewDir"
#define INDEX_URL URL_ROOT "/index.html"
#define TUTORIAL_URL URL_ROOT "/tutorial.html"

MainWindow::MainWindow(QWidget* parent)
  : QMainWindow(parent /*, Qt::FramelessWindowHint*/)
{
  ui.setupUi(this);

  connect(ui.btn_newBrowser, &QPushButton::clicked, this, &MainWindow::onBtnNewBrowserClicked);
  connect(ui.btn_quitApp, &QPushButton::clicked, qApp, &QCoreApplication::quit);
  connect(ui.btn_changeColor, &QPushButton::clicked, this, &MainWindow::onBtnChangeColorClicked);

  QVBoxLayout* layout = new QVBoxLayout();
  layout->setContentsMargins(2, 2, 2, 2);
  layout->setSpacing(3);
  layout->addWidget(ui.nativeContainer);
  layout->addWidget(ui.cefContainer);
  centralWidget()->setLayout(layout);

  // build the path to the web resource
  QDir dir = QCoreApplication::applicationDirPath();
  QString webResourceDir =  QDir::toNativeSeparators(dir.filePath("webApp"));

  // add a local folder to URL map (global)
  QCefContext::instance()->addLocalFolderResource(webResourceDir, URL_ROOT);

  createCefView();
}

MainWindow::~MainWindow() {}

void MainWindow::createCefView()
{
  ///*
  // build settings for per QCefView
  QCefSetting setting;
  setting.setPlugins(false);
  setting.setWindowlessFrameRate(60);
  setting.setBackgroundColor(QColor::fromRgba(qRgba(255, 255, 220, 255)));
  // setting.setBackgroundColor(Qt::blue);
  
  // create the QCefView widget and add it to the layout container
  cefViewWidget = new CefViewWidget(INDEX_URL, &setting);

  ui.cefContainer->layout()->addWidget(cefViewWidget);

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

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

  // connect the cefQueryRequest to the slot
  connect(cefViewWidget, &QCefView::cefQueryRequest, this, &MainWindow::onQCefQueryRequest);

}


void MainWindow::onBtnNewBrowserClicked()
{
	QMainWindow* w = new QMainWindow(nullptr);
	w->setAttribute(Qt::WA_DeleteOnClose);

	QCefSetting settings;
	QCefView* view = new QCefView(INDEX_URL, &settings, w);

	w->setCentralWidget(view);
	w->resize(1024, 768);
	w->show();
}


void MainWindow::onBtnChangeColorClicked()
{
	if (cefViewWidget) 
	{
		// 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
		cefViewWidget->broadcastEvent(event);
	}
}

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 sTitle("消息提醒");
		QString text = QString("Current Thread: QT_UI\r\n"
			"BrowserId: %1\r\n"
			"Frame: %2\r\n"
			"Method: %3\r\n"
			"Arguments:\r\n")
			.arg(browserId)
			.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(), sTitle, text);
	}
}

void MainWindow::onQCefQueryRequest(int browserId, int64_t frameId, const QCefQuery& query)
{
	QMetaObject::invokeMethod(
		this,
		[=]()
	{
		QString sTitle("消息提醒");
		QString text = QString("Current Thread: QT_UI\r\n"
			"Query: %1")
			.arg(query.request());

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

		QString response = query.request().toUpper();
		query.setResponseResult(true, response);
		cefViewWidget->responseQCefQuery(query);
	},
		Qt::QueuedConnection);
}

3、创建一个简单的Web页面

创建一个简单的Web页面,内容如下:

<html>
  <head>
  <meta charset="utf-8">
  </head>
  <body onload="onLoad()" id="main" class="noselect">
    <h1 align="center" style="font-size: 12pt">Web Area</h1>
	<label> InvokeMethod方法测试:向桌面端发送消息 </label>
    <br />
    <input
      type="button" value="Invoke Method"
      onclick="onInvokeMethodClicked('TestMethod', 1, false, 'arg3')"/>
	<br />
	<br />
	<label> 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()" />
  </body>
  
  <script>
	function onLoad()
	{
        if (typeof CallBridge == "undefined") 
		{
          alert("Not in CefView context");
          return;
        }

        CallBridge.addEventListener("colorChange", function (color) 
		{document.getElementById("main").style.backgroundColor = color;});
    }
	  
    function onInvokeMethodClicked(name, ...arg) 
	{
      // invoke C++ code
      window.CallBridge.invokeMethod(name, ...arg);
    }
	
	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);
      }
	
  </script>
</html>

三、功能分析

1、加载WebApp资源

QCefView提供4种加载Web资源的方式。

  • 加载在线Web内容

在QCefView的构造函数中直接传递在线Web内容的URL

  // build settings for per QCefView
  QCefSetting setting;
  // create the QCefView widget and add it to the layout container
  QCefView* cefView = new QCefView("https://baidu.com", &setting, nullptr);
  • 通过本地文件路径加载

在QCefView的构造函数中直接传递本地Web资源文件的全路径,注意路径必须是以file://为schema的格式。

 // build the path to the web resource
  QDir dir = QCoreApplication::applicationDirPath();
  QString webResourceDir = QDir::toNativeSeparators(dir.filePath("webres/index.html"));

  // build settings for per QCefView
  QCefSetting setting;

  // create the QCefView widget and add it to the layout container
  QCefView* cefView = new QCefView(INDEX_URL, &setting, nullptr);
  • 添加本地文件目录到URL的映射

如果你的WebApp资源文件较多,并且在一个本地目录中,你可以通过如下方法添加一个本地文件目录到URL的映射:

public void addLocalFolderResource(const QString & path,const QString & url,int priority);

例如WebApp经过编译后输出到webres目录中,其目录结构如下:

full\path\to\webres
                │   index.html
                ├───assets
                ├───docs
                ├───img

可以通过以下方法添加映射:

// add a local folder to URL map
  QCefContext::instance()->addLocalFolderResource(
      "full\\path\\to\\webres", 
      "https://domainname"              // This could be any URL you need
      );

  // build settings for per QCefView
  QCefSetting setting;

  // create the QCefView widget and add it to the layout container
  QCefView* cefView = new QCefView(
      "https://domainname/index.html", 
      &setting, 
      this
      );

映射添加之后,便可以通过URL拼接资源相对路径的方式访问所有资源。

  • 添加本地Zip文件到URL的映射

除了添加本地文件目录到URL的映射,还可以添加本地Zip文件到URL的映射,通过以下方法实现:

public void addArchiveResource(const QString & path,const QString & url,const QString & password);

例如以下结构的Zip文件

full\path\to\webres.zip
                │   index.html
                ├───assets
                ├───docs
                ├───img

通过以下代码添加映射:

 // add a local zip file to URL map
 QCefContext::instance()->addArchiveResource(
     "full\\path\\to\\webres.zip", 
     "https://domainname",
     "password"                 // pass the password of the zip file if needed
     );

  // build settings for per QCefView
  QCefSetting setting;

  // create the QCefView widget and add it to the layout container
  QCefView* cefView = new QCefView(
      "https://domainname/index.html", 
      &setting, 
      this
      );

使用场景
如果你使用比较流行的前端框架(Rect,Vue或者其他框架)开发你的WebApp,那么上述方法3和4非常有用,特别是SAP WebApp。这些前端项目编译之后输出的静态资源结构比较复杂,所以可以通过添加映射的方式来加载。

2、C++/Javascript互操作

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

互操作能力是通过在QCefView所创建的所有Browser和Frame中插入桥接对象实现的。桥接对象提供一些方法用于原生代码和Web代码进行通信,更多详细介绍请参考WebAPIs。

桥接对象被挂载在window对象上,并且可以通过QCefConfig::setBridgeObjectName来设置桥接对象的名字。默认的对象名为CefViewClient

从Javascript中调用C++
桥接对象提供以下方法来从Javascript中调用C++:

invokeMethod(name, ...args),

当该方法在Javascript中调用后,下面的Qt signal将被触发:

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

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

现在让我们编写一段代码来演示如何从Javascript中调用C++。

添加Javascript代码
添加如下代码在Web页面的<script>代码块中:

function onInvokeMethodClicked(name, ...arg) {
  // invoke C++ code
  window.CallBridge.invokeMethod(name, ...arg);
}

添加如下HTML代码:

<label> Test Case for InvokeMethod </label>
<br />
<input
  type="button"
  value="Invoke Method"
  onclick="onInvokeMethodClicked('TestMethod', 1, false, 'arg3')"
/>

添加C++代码
然后添加如下C++代码用于处理调用请求:

MainWindow::MainWindow(QWidget* parent)
  : QMainWindow(parent)
{
  // ...

  // connect the invokeMethod to the slot
  connect(cefViewWidget,
          SIGNAL(invokeMethod(int, int, const QString&, const QVariantList&)),
          this,
          SLOT(onInvokeMethod(int, int, const QString&, const QVariantList&)));
  // ...
}

void MainWindow::onInvokeMethod(int browserId, int frameId, const QString& method, const QVariantList& arguments)
{
  // extract the arguments and dispatch the invocation to corresponding handler
  if (0 == method.compare("TestMethod")) {
    QMetaObject::invokeMethod(
      this,
      [=]() {
        QString title("QCef InvokeMethod Notify");
        QString text = QString("Current Thread: QT_UI\r\n"
                               "Method: %1\r\n"
                               "Arguments:\r\n")
                         .arg(method);

        for (int i = 0; i < arguments.size(); 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
        }

        QMessageBox::information(this->window(), title, text);
      },
      Qt::QueuedConnection);
  } else {
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欧特克_Glodon

很高兴能帮助到您!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值