目录
前言
在上一篇(QCefView(1)—— CMAKE项目、库文件生成和项目测试)同大家一起分享了QCefView的编译环境搭建、库文件编译,和测试项目运行等,本章,主要和大家分享和研究下基于QCefView实现的Qt与JS通讯的部分。
主要项目文件
在测试项目中我们看到 customcefview.h文件中:
#ifndef CUSTOMCEFVIEW_H
#define CUSTOMCEFVIEW_H
#include <include/QCefView.h>
#include "configurationTool/configuration.h"
#include "MessageDistribution.h"
class CustomCefView : public QCefView
{
Q_OBJECT
public:
CustomCefView(const QString& url, QWidget *parent);
~CustomCefView();
/*通过事件注册的方式,将消息发送给JS*/
void changeColor(); // 背景颜色发生改变
void languageChange(QString language); // 语言发生改变的槽函数
/*接收JS端发送给Qt的消息*/
protected slots:
virtual void onQCefUrlRequest(const QString& url) override;
virtual void onQCefQueryRequest(const QCefQuery& query) override;
virtual void onInvokeMethodNotify(int browserId, int frameId, const QString& method, const QVariantList& arguments) override;
void onLoadingStateChanged(bool isLoading, bool canGoBack, bool canGoForward) override;
void onLoadStart() override;
void onLoadEnd(int httpStatusCode) override;
void onLoadError(int errorCode, const QString &errorMsg, const QString &failedUrl) override;
private:
Configuration configuration; // 通讯配置工具
MessageDistributionHandle msgHandle;// 消息分发处理
};
#endif // CUSTOMCEFVIEW_H
对应的customcefview.cpp 中,我们来看如何实现事件注册,以及接收到JS消息后,Qt如何处理的:
#include <windows.h>
#include <QMessageBox>
#include <QColor>
#include "customcefview.h"
CustomCefView::CustomCefView(const QString& url, QWidget* parent)
: QCefView(url, parent)
{}
CustomCefView::~CustomCefView() {}
/// 注册一个窗体颜色改变的事件==》Qt发送消息到网页端,网页端接受该事件消息后响应
void CustomCefView::changeColor()
{
qsrand(::GetTickCount());
QColor color(qrand());
/****************************************************************/
QCefEvent event("colorChangedEvent");
event.setStringProperty("color", color.name()); // 定义事件属性参数
broadcastEvent("colorChange", event); // 广播事件
/****************************************************************/
}
//// 注册一个语言槽函数
void CustomCefView::languageChange(QString language) {
QCefEvent event("languageChangedEvent");
event.setStringProperty("language", language);
broadcastEvent("languageChange", event);
}
void CustomCefView::onQCefUrlRequest(const QString& url)
{
QString title("QCef Url Request ...[function] onQCefUrlRequest");
QString text = QString("Current Thread: QT_UI\r\n"
"Url: %1")
.arg(url);
QMessageBox::information(this->window(), title, text);
}
/// 接收由JS发送过来的消息并回包
void CustomCefView::onQCefQueryRequest(const QCefQuery& query)
{
QString title("QCef Query Request");
QString text = QString("Current Thread: QT_UI\r\n"
"Query: %1")
.arg(query.reqeust());
QMessageBox::information(this->window(), title, text);
QString response = query.reqeust().toUpper();
query.setResponseResult(true, response);
responseQCefQuery(query);
}
/// 接受由JS发送的消息
void CustomCefView::onInvokeMethodNotify(int browserId, int frameId, const QString& method, const QVariantList& arguments)
{
/** 注意:JS端通过调用 invokeMethod 方法,实际上会有两个参数变量传递过来,
** 一个是 const QString& method, 通过method我们可以进行消息过滤,在Qt中进行消息分发处理
** 另一个是const QVariantList& arguments,通过变量类型我们可知,这个参数同时可以传递多种
** 不同类型的数据到Qt中来,我们可以通过解析消息体,做具体的业务处理。
**/
qDebug() << "[browserId]:" << browserId << " [frameId]:" << frameId << " [method]:" << method << "[arguments]:" << arguments;
if (0 == method.compare("onDragAreaMouseDown")) { // for test
HWND hWnd = ::GetAncestor((HWND)getCefWinId(), GA_ROOT);
// get current mouse cursor position
POINT pt;
::GetCursorPos(&pt);
// in case the mouse is being captured, try to release it
::ReleaseCapture();
// simulate that the mouse left button is down on the title area
::SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, POINTTOPOINTS(pt));
return;
}
else if (0 == method.compare("configurationTool")) { // 调用通讯配置工具
configuration.show();
}
else if (0 == method.compare("openLuaEditor")) { // 打开lua脚本编辑器
QString Luaparam(arguments.last().value<QString>());
msgHandle.luaScriptEditHandle(Luaparam);
}
else {
QString title("QCef InvokeMethod Notify");
QString text = QString("Current Thread: QT_UI\r\n"
"Method: %1\r\n"
"Arguments: ...")
.arg(method);
QMessageBox::information(this->window(), title, text);
}
}
void CustomCefView::onLoadingStateChanged(bool isLoading, bool canGoBack, bool canGoForward)
{
qDebug() << "isLoading:" << isLoading << "canGoBack" << canGoBack << "canGoForward" << canGoForward;
}
void CustomCefView::onLoadStart()
{
qDebug() << "onLoadStart";
}
void CustomCefView::onLoadEnd(int httpStatusCode)
{
qDebug() << "onLoadEnd" << httpStatusCode;
}
void CustomCefView::onLoadError(int errorCode, const QString &errorMsg, const QString &failedUrl)
{
qDebug() << "onLoadError" << errorCode << errorMsg << failedUrl;
}
Qt与JS通讯
1、Qt发送消息给JS部分:
void CustomCefView::changeColor()
{
qsrand(::GetTickCount());
QColor color(qrand());
/***********************以注册消息事件的方式将消息从Qt发送到JS*******/
QCefEvent event("colorChangedEvent");
event.setStringProperty("color", color.name()); // 定义事件属性参数
broadcastEvent("colorChange", event); // 广播事件
/****************************************************************/
}
2、Qt接收JS消息部分:
从上面代码中可以看到,实际上有三种接收的方式,这里我们同样只关注因为invokeMethod()方法JS发送给Qt的消息处理
void CustomCefView::onInvokeMethodNotify(int browserId, int frameId, const QString& method, const QVariantList& arguments)
{
/** 注意:JS端通过调用 invokeMethod 方法,实际上会有两个参数变量传递过来,
** 一个是 const QString& method, 通过method我们可以进行消息过滤,在Qt中进行消息分发处理
** 另一个是const QVariantList& arguments,通过变量类型我们可知,这个参数同时可以传递多种
** 不同类型的数据到Qt中来,我们可以通过解析消息体,做具体的业务处理。
**/
qDebug() << "[browserId]:" << browserId << " [frameId]:" << frameId << " [method]:" << method << "[arguments]:" << arguments;
if (0 == method.compare("onDragAreaMouseDown")) { // for test
HWND hWnd = ::GetAncestor((HWND)getCefWinId(), GA_ROOT);
// get current mouse cursor position
POINT pt;
::GetCursorPos(&pt);
// in case the mouse is being captured, try to release it
::ReleaseCapture();
// simulate that the mouse left button is down on the title area
::SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, POINTTOPOINTS(pt));
return;
}
else if (0 == method.compare("configurationTool")) { // 调用通讯配置工具
configuration.show();
}
else if (0 == method.compare("openLuaEditor")) { // 打开lua脚本编辑器
QString Luaparam(arguments.last().value<QString>());
msgHandle.luaScriptEditHandle(Luaparam);
}
else {
QString title("QCef InvokeMethod Notify");
QString text = QString("Current Thread: QT_UI\r\n"
"Method: %1\r\n"
"Arguments: ...")
.arg(method);
QMessageBox::information(this->window(), title, text);
}
}
对照我们看看在html中如何处理Qt与JS通讯部分具体的消息收发的:
<html>
<head>
<script>
// 捕获Qt发送给JS事件信号的函数
function onColorChanged(event) {
document.getElementById("main").style.backgroundColor = event["color"];
alert(event["color"]);
}
function onLanguageChanged(event) {
OpenPopupEx(event["language"]); // ==》接收到Qt语言改变的消息
QCefClient.invokeMethod("JsGetLanguageChanged", 1, false, "arg3");//==>返回确认信息给Qt
}
/// 在页面加载时增加事件监听
function onLoad() {
if (typeof (QCefClient) == 'undefined') {
return;
}
// 增加颜色改变事件的监听
QCefClient.addEventListener("colorChange", onColorChanged);
QCefClient.addEventListener("languageChange", onLanguageChanged);
}
/// 在此说明,invokeMethod("funcname",...)中可以自定义传入多个参数类型
/// 在Qt中将以QVariantList& arguments 变量接收 ==> eg: [arguments]: (QVariant(int, 1), QVariant(bool, false), QVariant(QString, "configurationTool"))
function onInvokeMethodClicked() {
QCefClient.invokeMethod("TestMethod", 1, false, "arg3");
}
/// 打开通讯配置工具
function onOpenConfigurationTool() {
QCefClient.invokeMethod("configurationTool", 1, false, "configurationTool");
}
/// 打开lua脚本编辑器
function onOpenLuaEditor() {
QCefClient.invokeMethod("openLuaEditor", 1, false, document.getElementById("luamessage").value);
}
function onDragAreaMouseDown() {
QCefClient.invokeMethod("onDragAreaMouseDown");
}
/// 使用QCefQuery 发送信息给Qt
function onQCefQueryClicked() {
var query = {
request: document.getElementById("message").value,
onSuccess: function (response) {
alert(response);
},
onFailure: function (error_code, error_message) {
alert(error_message);
}
}
window.QCefQuery(query);
}
/**
* getFuntionName()
* @param {Function} func
* @return {String}
*/
function getFunctionName(func) {
if (typeof func == 'function' || typeof func == 'object') {
var name = ('' + func).match(/function\s*([\w\$]*)\s*\(/);
}
return name && name[1];
}
/**
* trace
* @param [int] [count=10]
* @return {String}
*/
trace = function () {
var stack = [],
caller = arguments.callee.caller;
while (caller) {
stack.unshift(getFunctionName(caller));
caller = caller && caller.caller;
}
return 'functions on stack:' + '\n' + stack.join('\n');
}
function OpenPopup() {
var popup = window.open("", "PopupWindow", "width=800, height=600");
popup.document.write("<p>This is popup window.</p>");
}
function OpenPopupEx(msg) {
var popup = window.open("", "PopupWindow", "width=800, height=600");
popup.document.write("<p>" + msg + "</p>");
}
</script>
</head>
<body onload="onLoad()" id="main">
<h1 align="center" style="font-size:12pt; font-family:MS Shell Dlg 2;">Web Area</h1>
<!--<div align="center" id="dragarea" style="height:40px; background-color: gray; color: white;" onmousedown="onDragAreaMouseDown()">
<span style="font-size:18pt; font-family:MS Shell Dlg 2;">you can drag this window in this area!</span>
</div>-->
<div align="center">
<label> Test Case for InvokeMethod </label>
<br />
<input type="button" value="Invoke Method" onclick="onInvokeMethodClicked()" />
<br />
<br />
<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="onQCefQueryClicked()" />
<br />
<br />
<label>Test Case for QCefUrlQuery</label>
<br />
<!--<a href="qcef://test/a/b">qcef://test/a/b</a>-->
<a href="http://www.baidu.com/">百度</a>
<br />
<br />
<button onclick="OpenPopup()">Open Popup</button> <input type="button" value="Open ConfigurationTool" onclick="onOpenConfigurationTool()" />
<input type="button" value="Open LuaEditor" onclick="onOpenLuaEditor()" />
<br />
<textarea id="luamessage" style="width:600px; height:300px;">{"data" : { "rows" : { "flag_addr_label_id" : "0","font_size" : 11,"id" : 0,"name" : "test","save_bit_address_flag" : 0, "text" :"getHmiModel()","trigger_addr_label_ids" :"","trigger_addr_labels" : "","trigger_type" : 0,"type" : 1,"var_list" : ""}}}"
</textarea>
<br />
</div>
</body>
</html>
3、JS接收Qt消息部分:
function onLoad() {
if (typeof (QCefClient) == 'undefined') {
return;
}
// 增加颜色改变事件的监听
QCefClient.addEventListener("colorChange", onColorChanged);
QCefClient.addEventListener("languageChange", onLanguageChanged);
}
在onLoad() 方法中实际上是做了一个事件监听的,对应我们在Qt端注册的 ”colorChange“ 和"languageChange",当Qt端调用changeColor ()和languageChange()方法时,会触发对应的事件,JS端捕获该事件并做处理,实际上就是实现了Qt(发)==》JS(收)的一个通讯过程。
4、JS发送消息给Qt:
在QCefView封装中,我们可以看到,JS发送消息给Qt实际上是有三种方式的,这里我们只重点关注invokeMethod() 方法:
function onOpenConfigurationTool() {
QCefClient.invokeMethod("configurationTool", 1, false, "configurationTool");
}
从上可以看到,在调用invokeMethod()方法时候,传递了4个参数,分别是:
method : [ string ] "configurationTool"
arguments: [ int ] 1
[ bool ] false
[ string ] "configurationTool"
结合我们在Qt讲到的接收JS消息和对应处理,一目了然了。