QT集成CEF13-实现JavaScript窗口间通信

本节利用CEF的进程间通信机制,实现在两个Renderer进程间,使用JavaScript进行通信。即实现两个窗口: MasterWindow 和 SlaverWindow,MasterWindow 使用JavaScript发送消息,SalverWindow监听处理消息。

其实现思路为:

  1. Browser进程中创建两个窗口,MasterWindow,加载本地 index.html, SlaverWindow加载本地 slaver.html,并保存两个窗口对应的browser对象在 Browser进程的内存中。
  2. MasterWindow对应的Renderer进程向 Browser进程发送消息,Browser进程收到消息后找到 SlaverWindow对应的browser对象,然后将消息再转发到SlaverWindow对应的Renderer进程
  3. SlaverWindow 对应的Renderer进程收到消息后,找到注册的JavaScript回调函数然后调用

可见两个Renderer进程之间实现JavaScript的通信实通过Brower进程中转消息实现的。

在这里插入图片描述

1. Browser进程侧

创建浏览器窗口,这里使用通过判断机器上如果有两块屏幕则创建slaverBrowser。主要用于主屏和副屏的应用场景。

1.1 创建Browser对象

主要代码 mainwindow.cpp如下:

// mainwindow.cpp
// 创建浏览器对象
bool MainWindow::createBrowser(QString url, QWidget* parentWin) {
	//获取实例对象
	CefRefPtr<QyCefClientHandler> handler(QyCefClientHandler::getInstance());
	// 浏览器配置,
	CefBrowserSettings browser_settings;
	//browser_settings.universal_access_from_file_urls = STATE_DISABLED;
	CefWindowInfo window_info;
	RECT winRect;
	QRect qtRect = parentWin->rect();
	winRect.left = qtRect.left();
	winRect.top = qtRect.top();
	winRect.right = qtRect.right();
	winRect.bottom = qtRect.bottom();
	HWND wnd = (HWND)parentWin->winId();
	window_info.SetAsChild(wnd, winRect);
	// Create the  browser window.
	return CefBrowserHost::CreateBrowser(window_info, handler, url.toStdString(), browser_settings,
		nullptr, nullptr);
}
/// <summary>
/// 创建浏览器窗体,并与QT 窗体集成
/// </summary>
void MainWindow::createBrowserWindow() {
	//运行目录
	QDir dir = QCoreApplication::applicationDirPath();
	// 要打开的网址
	QString uriMaster = QDir::toNativeSeparators(dir.filePath("resources/index.html"));
	// 创建MasterBrowser
	createBrowser(uriMaster, this->centralWidget());

	// 操作系统桌面
	QDesktopWidget* desktop = QApplication::desktop();
	if (desktop->screenCount() <= 1) {
		qDebug() << QString("无第二屏幕:%1").arg(desktop->screenCount());
		return;
	}
	QString uriSlaver = QDir::toNativeSeparators(dir.filePath("resources/slaver.html"));
	m_slaverWin = new SlaverWindow(NULL);
	m_slaverWin->setGeometry(desktop->screenGeometry(1));
	// 创建浏览器窗口
	bool ret = createBrowser(uriSlaver, m_slaverWin);
	m_slaverWin->show();
	m_slaverWin->showFullScreen();
	//qDebug() << QString("SlaverWindow::createBrowserWindow======:%1").arg(ret);

}

1.2 保存Browser对象

当browser对象被创建后,会被QyCefClientHandler的 OnAfterCreated 监测到,此时可以在这里保存browser对象。

主要代码:

/// <summary>
/// 每弹出一个浏览器窗体,就会有一个新的browser
/// </summary>
/// <param name="browser"></param>
void QyCefClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
	CEF_REQUIRE_UI_THREAD();
	//const std::string type = getBrowserType(browser);
	//保存浏览器对象
	if (browserMap.size() == 0) {
		masterBrowser = browser;
	}
	else {
		slaverBrowser = browser;
	}
	browserMap[browser->GetIdentifier()] = browser;

	//qDebug() << QString("OnAfterCreated=======>frameID:%1 ,  total:  %2    browserID: %3")
	//	.arg(browser->GetMainFrame()->GetIdentifier())
	//	.arg(browserMap.size())
	//	.arg(browser->GetIdentifier());
}

参数 browser 中好像没有携带加载的页面信息,通过它找到的 mainFrame的url是空的,这里用了一个QMap来保存browser对象,第一次进来的 browser被认为是 maserBrowser。

如果有更好的办法,请留言

1.3 转发消息

当收到渲染进程发送的消息PROCESS_MESSAGE_TO_SLAVER_SCREEN 后,先找到 slaverBrowser浏览器对象,然后将消息转发给 SlaverWindow对应的Renderer进程:代码

// 收到渲染进程消息
bool QyCefClientHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	CefProcessId source_process,
	CefRefPtr<CefProcessMessage> message) {
	CefString messageName = message->GetName();
	std::string msgName = messageName;
	if (Share::PROCESS_MESSAGE_TO_SLAVER_SCREEN == msgName) {
		// 使用slaver browser对象转发消息
		if (slaverBrowser.get()) {
			slaverBrowser->GetMainFrame()->SendProcessMessage(PID_RENDERER, message);
			return true;
		}
	}
	return false;
}

2. Renderer进程侧

Renderer进程侧主要注册 JavaScript Native函数:

  • index.html 页面对应的

    • app.sendToSlaver(data)
    • app.cleanSlaver()
  • slaver.html页面对应的

    • app.onReceiveFromMaster(function(data){ ... })
    • app.onCleanData(function(){.....})

    CEF中发送消息的时候,都只能发送基本数据类型,如果要发送一个JavaScript 对象,就需要在JavaScript中使用 JSON.stringify 将对象转换成字符串进行传递

    收到后,需要用 JSON.parse 将字符串重新转换为 JavaScript 对象。

    如有更好办法,请留言

2.1定义函数处理器

ScreenCommunicationV8Handler 为自定义的 V8Handler 函数处理器。用来处理 上面 4 个 JavaScript函数的注册和回调。
要点:

  1. index.html 页面对应的 app.sendToSlaver(data)app.cleanSlaver() ,当他们执行后,需要将消息转发到 Browser进程。不能直接执行 slaver.html中注册的回调函数,因为这两个页面在不同的Render进程中,无法直接通信,需要借助 Browser进程来进行中转。
// 给Browser 进程发送消息
void ScreenCommunicationV8Handler::handleMaster(const CefString& name,
	const CefV8ValueList& arguments,
	CefRefPtr<CefV8Value>& retval,
	CefString& exception
) {
	// 发送消息给browser 进程
	//创建消息
	CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(Share::PROCESS_MESSAGE_TO_SLAVER_SCREEN);
	// 参数
	CefRefPtr<CefListValue> args = msg->GetArgumentList();
	//枚举转换为字符串
	qDebug() << "=====发送消息给Browser进程==function name:" << QString::fromStdString(name);
	if (name == "sendToSlaver") {
		args->SetSize(2); //2个参数
		args->SetString(0, name); //被调用的函数名称
		args->SetString(1, arguments[0]->GetStringValue());//参数内容

	}
	if (name == "cleanSlaver") {
		args->SetSize(1); //1个参数
		args->SetString(0, name); //被调用的函数名称
	}
	CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
	// 发送消息
	context->GetFrame()->SendProcessMessage(PID_RENDERER, msg);

}
  1. slaver.html页面对应的app.onReceiveFromMaster(function(data){ ... })app.onCleanData(function(){.....}) 注册后,要保存回调函数的引用到内存中,等待消息转发过来后,在从消息中取出数据,最后执行回调。
// 注册 JavaScript函数,保存回调函数
void ScreenCommunicationV8Handler::handleSlaver(const CefString& name,
	const CefV8ValueList& arguments,
	CefRefPtr<CefV8Value>& retval,
	CefString& exception) {
	CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
	JavaScriptCallBack callback = {
		name,context,context->GetFrame(),context->GetBrowser(),arguments[0]
	};

	// 回调函数加入到map中
	mCallBackMap.insert(name, callback);
	qDebug() << QString("ScreenCommunicationV8Handler::handleSlaver    name:%1  mCallBackMapSize: %2")
		.arg(QString::fromStdString(name)).arg(mCallBackMap.size());
}
  1. 当收到从Browser进程转发过来的消息后,需要执行回调,所以提供一个public的 方法:handleSlaverCallback
// 调用注册的回调函数
bool ScreenCommunicationV8Handler::handleSlaverCallback(CefRefPtr<CefProcessMessage> message) {
	// 解析message
	CefString name = message->GetName();

	if (name != Share::PROCESS_MESSAGE_TO_SLAVER_SCREEN) {
		return false;
	}
	CefRefPtr<CefListValue> args = message->GetArgumentList();
	CefString functionName = args->GetString(0);

	qDebug() << QString("ScreenCommunicationV8Handler::handleSlaverCallback : %1    functionName:%2   callbackSize:%3     ::::::%4")
		.arg(QString::fromStdString(name))
		.arg(QString::fromStdString(functionName))
		.arg(mCallBackMap.size())
		.arg(functionName == "sendToSlaver");
	if (functionName == "sendToSlaver") {
		CefString callbackParam = args->GetString(1);
		// 解析成JSON
		//CefRefPtr<CefValue> jsonObject = CefParseJSON(callbackParam, JSON_PARSER_ALLOW_TRAILING_COMMAS);
		//onReceiveFromMaster
		qDebug() << QString("调用callback之前 : ********%1").arg(mCallBackMap.contains("onReceiveFromMaster"));
		if (mCallBackMap.contains("onReceiveFromMaster")) {
			JavaScriptCallBack callback = mCallBackMap["onReceiveFromMaster"];

			CefV8ValueList arguments;

			qDebug() << QString("调用callback之前 :   FrameID :%1   url:%2   callbackParam:%3")
				.arg(callback.frame->GetIdentifier())
				.arg(QString::fromStdString(callback.frame->GetURL()))
				.arg(QString::fromStdString(callbackParam));

			//回调函数参数
			arguments.push_back(CefV8Value::CreateString(callbackParam));
			// 执行回调
			callback.callbackFun->ExecuteFunctionWithContext(callback.context, NULL, arguments);

			return true;
		}
		return false;
	}
	if (functionName == "cleanSlaver") {
		//onCleanData
		if (mCallBackMap.contains("onCleanData")) {
			JavaScriptCallBack callback = mCallBackMap["onCleanData"];
			// 进入Contxt
			//callback.context->Enter();
			CefV8ValueList arguments;
			// 执行回调
			callback.callbackFun->ExecuteFunctionWithContext(callback.context, NULL, arguments);
			//callback.context->Exit();
			return true;
		}
		return false;
	}
	return false;
}

2.2 收到Browser进程转发的消息

QyAppRenderer 中的OnProcessMessageReceived 收到从Browser进程转发的消息后,执行 Slaver.html注册的回调函数:

bool QyAppRenderer::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	CefProcessId source_process,
	CefRefPtr<CefProcessMessage> message) {
	// 收到 browser进程发送的消息
	CEF_REQUIRE_RENDERER_THREAD();
	DCHECK_EQ(source_process, PID_BROWSER);

	qDebug() << QString("收到browser 进程消息 : %1").arg(frame->GetIdentifier());
	if (mScreenCommunicationV8Handler) {
		return mScreenCommunicationV8Handler->handleSlaverCallback(message);
	}
	return false;
}

3. 页面端

3.1 MasterWindow 主屏

index.html

    <input type="text" id="txtMessage" />  
    <button id="btnSendSecondScreen">发送数据给副屏</button>
    <button id="btnCleanSecondScreen">副屏清屏</button>

    <div>主副屏进程间通信</div>
    <script src="js/jquery-3.6.0.min.js"></script>
    <script src="js/index.js"></script>

index.js

window.onload = () => {
    console.log(window.secondScreen);

    if(window.app && window.app.sendToSlaver){
        $("#btnSendSecondScreen").click(()=>{
            // 发送数据 sendData 发送的是字符串
            app.sendToSlaver(JSON.stringify({
                time:new Date(),
                content:$("#txtMessage").val()
            }));    
        });

        $("#btnCleanSecondScreen").click(()=>{
            // 清屏
            window.app.cleanSlaver();
        });
    }
    
}

在这里插入图片描述

3.2 SlaverWindow 副屏

slaver.html

    <div style="font-size: 25px;">这是副屏 本地HTML</div>

    <hr>
     收到的数据: <br />
    <div id="receivedData">

    </div>

    <script src="js/jquery-3.6.0.min.js"></script>
    <script src="js/slaver.js"></script>

slaver.js

window.onload=function(){
    
    if(window.app && window.app.onReceiveFromMaster){

        // 接收主屏发送数据 data是字符串
        window.app.onReceiveFromMaster((data)=>{
            var htmlData=$("#receivedData").html();
            htmlData+="<br />"+data;
            $("#receivedData").html(htmlData);
        }); 

        //清除数据
        window.app.onCleanData(()=>{
            $("#receivedData").html("");
        }); 
    }

}

3.3 运行效果

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
下面是在 Qt 窗口嵌入 CEF 窗口的基本步骤: 1. 在 Qt 项目中添加 CEF 的库文件和头文件: 在 Qt 项目中添加 CEF 的库文件和头文件,以便在 Qt 中使用 CEF 控件。 2. 创建 CEF 控件: 在 Qt 窗口中创建 CEF 控件,可以使用 CEF 提供的 API 创建控件,例如: ```cpp CefRefPtr<CefBrowser> browser = CreateBrowser(hwnd, url); ``` 其中,hwnd 是 Qt 窗口的句柄,url 是要加载的 Web 页面的地址。 3. 将 CEF 控件的窗口嵌入到 Qt 窗口中: 使用 Qt 提供的 API 将 CEF 控件的窗口嵌入到 Qt 窗口中,例如: ```cpp CefWindowHandle hwnd = browser->GetHost()->GetWindowHandle(); QWindow *window = QWindow::fromWinId((WId)hwnd); QWidget *widget = QWidget::createWindowContainer(window); ``` 其中,browser 是创建的 CEF 控件,hwnd 是 CEF 控件的窗口句柄,window 是 Qt 窗口对象,widget 是包含 CEF 控件窗口Qt 控件。 4. 将 Qt 控件添加到 Qt 窗口中: 在 Qt 窗口中添加包含 CEF 控件窗口Qt 控件,例如: ```cpp QVBoxLayout *layout = new QVBoxLayout(ui->centralWidget); layout->addWidget(widget); ``` 其中,ui->centralWidget 是 Qt 窗口的中心控件,layout 是 Qt 窗口的布局管理器。 这样,CEF 控件就可以嵌入到 Qt 窗口中了。 注意事项: 在将 CEF 控件嵌入到 Qt 窗口中时,需要注意以下几点: 1. Qt 窗口CEF 控件窗口之间的大小和位置关系需要正确设置,以保证 CEF 控件窗口完全嵌入到 Qt 窗口中。 2. 在使用 CEF 控件时,需要遵循 CEF 的许可协议。 3. 在使用 Qt 控件时,需要遵循 Qt 的许可协议。 4. 在将 CEF 控件嵌入到 Qt 窗口中时,需要注意相关的技术和代码的兼容性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

paopao_wu

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值