本文资源下载
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
下载 QCefView
QCefView官网 https://cefview.github.io/QCefView/
Github https://github.com/CefView/QCefVie
克隆代码
git clone https://github.com/CefView/QCefView.git
git clone https://github.com/CefView/CefViewCore.git
虽然QCefView_工程里有CefViewCore目录,但是是空的,需要手动clone下CefViewCore的代码,然后放到QCefView/CefViewCore的文件夹里面
在根目录下创建sdk_x64文件夹,并使用clion 下使用vs2022 识别项目,注意识别过程会自动下载cef的包
-G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./../sdk_x64 -DBUILD_DEMO=ON
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
public bool triggerEvent(const QCefEvent & event
public bool triggerEvent(const QCefEvent & event,int frameId)
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调用下列函数进行接收:
addEventListener(name, listener
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);
}