QT集成CEF12-JavaScript监听文件夹与文件变化

QT集成CEF12-JavaScript监听文件夹与文件变化

本节要实现的目标是: 监听本地磁盘文件变化:

  • 文件夹中新增文件
  • 文件夹中的文件被删除
  • 文件内容改变

一旦发生这些变化,这些变化将作为“事件”发送给HTML网页,网页中JavaScript 注册的监听函数开始执行处理事件。其实本质上实现的就是Browser进程发送消息到Render进程, Render进程执行 JavaScript注册的回调函数,同时传递从Browser进程发送过来的消息内容,最终消息的内容在JavaScript回调函数中处理。

主要步骤如下:

  • Browser进程侧:

    • 主窗体监听文件夹,使用 QFileSystemWatcher , 取得变化内容后封装成一个自定义的 Event结构体
    • 调用browser中的frame 的 SendProcessMessage 方法,将 自定义的Event结构体转换为 CefProcessMessage 发送给 Renderer进程。
  • Renderer进程侧:

    • 在 OnContextCreated 函数中注册JavaScript 函数,其函数原型为:

      // 添加事件监听器,后端有事件发生,会执行callback回调函数来让JavaScript来处理数据
      // eventName 事件名称,字符串
      // callback 当事件发生以后,执行的事件处理函数,原型为:
      //   callback(eventData) {}  evetnData为事件数据
      app.addEventListener(eventName,callback);
      
      // 删除时间监听
      app.removeEventListener(eventName);
      
      
    • 自定义 CefV8Handler,在这个Handler中保存所有注册的 callback函数

    • OnProcessMessageReceived 中接收Browser进程中发送的消息,取出消息内容

    • 遍历所有JavaScript注册的callback函数,并执行

    • JavaScript执行回调函数。

1. Browser进程侧

​ Browser进程侧主要完成文件变化监控,然后将监控到的消息发送到Renderer进程侧

1. 1 文件变化监控

定义一个枚举FileChangeEventType,表示文件变化类型. 定义一个事件FileChangeEvent,保存事件信息

// filesystemwatcher.h
// 文件变化类型
typedef enum _FileChangeEventType {
	// 新建文件夹或文件,删除文件夹或文件,新建文件或文件夹,删除文件或文件夹,更新文件, 重命名文件或文件夹
	NEW, REMOVE, UPDATE_FILE, RENAME
} FileChangeEventType;

// 文件变化事件内容
typedef struct _FileChangeEvent
{

	FileChangeEventType fileChangeEventType; //事件类型
	QString message; //消息
}FileChangeEvent;

可以使用 QFileSystemWatcher 监控文件/文件夹变化。一般应用场景中主要是对指定的文件夹进行监控,但是新增了哪个文件,删除了哪个文件无法监控到,需要对它做一些逻辑处理。这里自定义了 FileSystemWatcher 类对QFileSystemWatcher 做了封装:

// filesystemwatcher.h
class FileSystemWatcher : public QObject
{
	Q_OBJECT
public:
	explicit FileSystemWatcher(QObject* parent = 0);
	void addWatchPath(QString path);

signals:
	// 文件改变信号
	void onFileChangeEventTrigger(FileChangeEvent event);

public slots:
	void directoryUpdated(const QString& path);  // 目录更新时调用,path是监控的路径
	void fileUpdated(const QString& path);   // 文件被修改时调用,path是监控的路径


private:
	QFileSystemWatcher* m_pSystemWatcher;  // QFileSystemWatcher变量
	QMap<QString, QStringList> m_currentContentsMap; // 当前每个监控的内容目录列表
};

该类中定义了一个onFileChangeEventTrigger 信号,当文件发生改变时发射这个信号。这个信号会与 SimpleHandler 中的槽函数关联,SimpleHandler的槽函数会找到 浏览器中的frame,然后再发送信号。

filesystemwatcher.cpp 直接参考源代码

1.2 MainWindow中开启监控

mainWindow中定义成员变量m_FileSystemWatcher ,构造函数中实例化它,并开启监控。

mainWindow中创建浏览器窗体的时候,将FileSystemWatcher中的onFileChageEventTrigger 信号关联到SimpleHandler中的槽函数onFileChageEventTrigger

// mainwindow.h
// ... 省略
class MainWindow : public QMainWindow {
    // ... 省略
private:
    FileSystemWatcher* m_FileSystemWatcher;
}

// mainwindow.cpp
MainWindow::MainWindow(SimpleApp* cefApp, QWidget* parent)
	: QMainWindow(parent), m_cefApp(cefApp),
	m_cef_query_handler(new CefQueryHandler),
	m_FileSystemWatcher(new FileSystemWatcher(this)) //初始化FileSystemWatcher
{
	 // ... 省略
     // 开启监控
	QString watchPath = "E:\\tmp";
	m_FileSystemWatcher->addWatchPath(watchPath);
}
// ... 省略
// FileSystemWatcher 中的onFileChangeEventTrigger 信号关联到 SimpleHandler中的onFileChageEventTrigger 槽函数
void MainWindow::createBrowserWindow() {

	CefRefPtr<SimpleHandler> handler(new SimpleHandler(false, m_cef_query_handler));
	// 当文件发生变化时,通知到SimpleHandler
	connect(m_FileSystemWatcher, &FileSystemWatcher::onFileChangeEventTrigger, handler, &SimpleHandler::onFileChageEventTrigger);
	// ... 省略
}

1.3 SimpleHandler 中定义槽函数

定义槽函数onFileChageEventTrigger 在槽函数中找到浏览器对象,然后找到frame向render进程发送消息。

/// <summary>
/// 文件发生变化槽函数
/// </summary>
/// <param name="fileChangeEvent"></param>
void SimpleHandler::onFileChageEventTrigger(FileChangeEvent fileChangeEvent) {
    // 要与Renderer接收端保持一致
	std::string notify_message = "FILE_CHAGE_EVENT_TRIGGER_NOTIFY";
	//创建消息
	CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(notify_message);
	//枚举转换为字符串
	QString str = "NEW,REMOVE,UPDATE_FILE,RENAME";
	QStringList list2 = str.split(",");
	std::string eventType = list2[fileChangeEvent.fileChangeEventType].toStdString();
	qDebug() << "=====发送消息给Renderer进程=======event Type:" << QString::fromStdString(eventType);
	// 发送消息给 Renderer进程
	CefRefPtr<CefListValue> args = msg->GetArgumentList();

	args->SetSize(2); //2个参数
	args->SetString(0, eventType); //事件类型
	args->SetString(1, fileChangeEvent.message.toStdString());//消息内容
	// 发送消息给Browser进程
	// 获取浏览器对象
	if (browser_list_.empty()) {
		return;
	}
	CefRefPtr<CefFrame> frame = browser_list_.front()->GetMainFrame();
	// 给Renderer 进程发送消息
	frame->SendProcessMessage(PID_RENDERER, msg);
}

2. Renderer进程侧

​ 首先考虑我们需要为window对象绑定两个函数:

// 添加事件监听器,后端有事件发生,会执行callback回调函数来让JavaScript来处理数据
// eventName 事件名称,字符串
// callback 当事件发生以后,执行的事件处理函数,原型为:
//   callback(eventData) {}  evetnData为事件数据
app.addEventListener(eventName,callback);

// 删除时间监听
app.removeEventListener(eventName);

使用它们可以注册事件监听函数。

注册的callback要映射到c++的native方法上。

2.1 绑定addEventListener与removeEventListener

绑定有回调的JavaScript函数这里选择在 OnContextCreated 函数中进行:

// QyAppRenderer.h
class QyAppRenderer :public CefApp, public CefRenderProcessHandler {
 // ...省略
    private:
	// 浏览器
	CefRefPtr<CefBrowser> m_mainBrowser;
	AppEventListenerV8Handler* m_appEventListenerV8Handler;
}

// QyAppRenderer.cpp
void QyAppRenderer::OnBrowserCreated(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefDictionaryValue> extra_info) {
	//保存浏览器对象
	m_mainBrowser = browser;
}
void QyAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	CefRefPtr<CefV8Context> context) {
	// 注册 window.cefQuery
	//注册监听器
	 Retrieve the context's window object.
	CefRefPtr<CefV8Value> window = context->GetGlobal();

	//创建一个JavaScript 对象,全部为只读属性
	CefRefPtr<CefV8Value> appObject = CefV8Value::CreateObject(NULL, NULL);

	// JavaScript 函数就需要 CefV8Handler 来处理。这里使用了前面定义的 AppNativeV8Handler
	m_appEventListenerV8Handler = new AppEventListenerV8Handler();
	CefRefPtr<CefV8Value> funcAddEventListener = CefV8Value::CreateFunction("addEventListener", m_appEventListenerV8Handler);
	CefRefPtr<CefV8Value> funcRemoveEventListener = CefV8Value::CreateFunction("removeEventListener", m_appEventListenerV8Handler);
	appObject->SetValue("addEventListener", funcAddEventListener, V8_PROPERTY_ATTRIBUTE_NONE);
	appObject->SetValue("removeEventListener", funcRemoveEventListener, V8_PROPERTY_ATTRIBUTE_NONE);

	//绑定到window对象上,同样为只读属性
	window->SetValue("app", appObject, V8_PROPERTY_ATTRIBUTE_READONLY);
}

这里创建了一个JavaScript中的 app对象,然后在 app中挂载了两个函数:addEventListener 用来注册监听器,removeEventListener 用来移除监听。

这两个JavaScript函数的调用由自定义的AppEventListenerV8Handler 类来处理

2.2 AppEventListenerV8Handler 处理器

在处理器中,需要对注册进来的 JavaScript回调函数保存起来,以便在收到 Browser进程的消息后能够调用到。

它除了保存回调函数,还要提供一个函数,能够共外部调用,触发回调函数的调用。

// app_event_listener_v8_handler.h

/// <summary>
/// 将回调函数和上下文包装在一起
/// </summary>
typedef struct _EventListener
{
	CefRefPtr<CefV8Value> callback_; // 注册的回调函数
	CefRefPtr<CefV8Context> context_;// javaScript 上下文
} EventListener;

typedef QMap < QString, QList<EventListener>> CallbackMap;

class AppEventListenerV8Handler :public CefV8Handler
{

public:
	bool Execute(const CefString& name, //JavaScript 函数名
		CefRefPtr<CefV8Value> object,     //JavaScript函数持有者
		const CefV8ValueList& arguments,//JavaScript 参数
		CefRefPtr<CefV8Value>& retval,  // JavaScript返回值
		CefString& exception) override;

	// 执行通知,调用回调函数
	void executeNotify(QString eventType, QString content);
private:
	// 保存回调函数映射
	CallbackMap m_callbackMap;

	IMPLEMENT_REFCOUNTING(AppEventListenerV8Handler);
};

app_event_listener_v8_handler.cpp 见github 源码

这里对其中的 executeNotify 做一下说明:在这个函数中,我们是需要调用JavaScript注册的回调函数的。这里一定要注意,根据事件名称取出了所有注册的回调函数以后,**一定要获取上下文后,调用上下文的Enter() 函数 ** 进入上下文。因为JavaScript的执行一定是要在上下文中的。

当消息到来的时候,此时就调用JavaScript的回调是无法完成的。因为此时的JavaScript已经离开了 Context,所以要获取到Context后进入以后才能执行JavaScript 回调。

void AppEventListenerV8Handler::executeNotify(QString eventType, QString content) {
	// 文件夹改变
	if (m_callbackMap.contains(eventType)) {
		QList<EventListener> callbackList = m_callbackMap[eventType];
		qDebug() << QString("AppEventListenerV8Handler::executeNotify====>%1===>%2").arg(eventType).arg(callbackList.size());
		for (auto it = callbackList.begin(); it != callbackList.end(); ++it) {
			// 回调函数参数值
			CefRefPtr<CefV8Value> callbackFunctionParam = CefV8Value::CreateString(content.toStdString());
			CefV8ValueList arguments;
			arguments.push_back(callbackFunctionParam);
			// 执行JavaScript回调,并将参数传递给它,参数是一个CefV8ValueList
			CefRefPtr<CefV8Context> context = (*it).context_;
			CefRefPtr<CefV8Value> callback = (*it).callback_;
			context.get()->Enter();//进入上下文
			callback->ExecuteFunction(NULL, arguments);
			context.get()->Exit();//退出上下文
		}
	}
}

2.4 接收Browser 进程消息

最后就是接收Browser进程额消息了。当Browser 进程有消息发送过来的时候,会执行OnProcessMessageReceived ,所以需要在 QyAppRenderer 中实现 OnProcessMessageReceived 函数:

从 CefProcessMessage 中取出传递过来的消息,然后调用 AppEventListenerV8Handler 中的executeNotify 函数,将消息交给回调函数执行带入到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);

	// 取得消息的名字
	QString msgName = QString::fromStdString(message.get()->GetName());
	if (msgName == "FILE_CHAGE_EVENT_TRIGGER_NOTIFY") { //与 Browser进程中的一致
		//取出内容
		CefString eventType = message.get()->GetArgumentList().get()->GetString(0); //第0个参数
		CefString content = message.get()->GetArgumentList().get()->GetString(1); //第1个参数

		qDebug() << "收到browser 进程消息:" << QString::fromStdString(content);
		// 调用JS 回调
		m_appEventListenerV8Handler->executeNotify(QString::fromStdString(eventType), QString::fromStdString(content));
		return true;
	}

	return m_message_router->OnProcessMessageReceived(browser, frame, source_process, message);
}

2.4 JavaScript 注册回调

现在要接收文件夹变化,只需要在 JavaScript中注册监听器即可:

// index.js 

window.onload = () => {
    console.log(window.app);
    if (window.app && window.app.addEventListener) {

        function handleFileChangeEvent(data) {
            // 将结果放入到div中
            var html = $("#msgContent").html() + "<br />" + data;
            $("#msgContent").html(html);
            console.log("=======>" + data);
        }

        //NEW, REMOVE, UPDATE_FILE, RENAME
        // 添加监听函数
        app.addEventListener("NEW", handleFileChangeEvent);
        app.addEventListener("REMOVE", handleFileChangeEvent);
        app.addEventListener("UPDATE_FILE", handleFileChangeEvent);
        app.addEventListener("RENAME", handleFileChangeEvent);
    }
}

3. 编译运行

Browser进程中,监控的是 E:\tmp 文件夹,所以在这个文件夹中添加删除文件,文件夹测试 一下:

在这里插入图片描述
在这里插入图片描述

代码请访问 GitHub qt_cef_12分支

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CEF(Chromium Embedded Framework)是一个基于Chromium的开源框架,主要用于嵌入式浏览器应用程序的开发Qt是一个跨平台的C++应用程序开发框架,提供了许多GUI和多媒体功能。在以下步骤中,我们将介绍如何将CEFQt集成。 1. 下载CEF 首先,需要从CEF官网(https://cefsharp.github.io/)下载适合您操作系统的CEF版本。您可以选择标准版本或使用NuGet包。 2. 创建Qt项目 使用Qt Creator创建一个新的Qt项目。在项目设置中,确保您已选择正确的构建工具链和平台。在.pro文件中添加以下内容: ``` LIBS += -L/path/to/cef -lcef INCLUDEPATH += /path/to/cef ``` 其中,`/path/to/cef`是CEF的安装路径。 3. 集成CEFQt项目中,您可以使用CEF的C API或C++ API。在本例中,我们将使用C++ API。 首先,将CEF的C++头文件添加到Qt项目中。在您的Qt项目中创建一个新的C++源文件,并使用以下代码初始化CEF: ``` #include "include/cef_app.h" #include "include/cef_browser.h" #include "include/cef_client.h" class CefApplication : public CefApp, public CefBrowserProcessHandler { public: // Implement CefApp methods here }; CefRefPtr<CefApplication> app(new CefApplication); CefMainArgs mainArgs(argc, argv); CefInitialize(mainArgs, app, NULL); ``` 这将初始化CEF并创建一个CEF应用程序。 接下来,您需要创建一个CEF客户端类。客户端类处理浏览器事件和渲染进程事件。以下是一个简单的CEF客户端类: ``` class CefClientImpl : public CefClient, public CefLifeSpanHandler { public: // Implement CefClient and CefLifeSpanHandler methods here }; CefRefPtr<CefClientImpl> client(new CefClientImpl); ``` 最后,将CEF嵌入到Qt应用程序中。在Qt应用程序的主窗口中,将CEF嵌入到QWidget中: ``` CefWindowInfo windowInfo; windowInfo.SetAsChild(reinterpret_cast<cef_window_handle_t>(widget->winId()), CefRect(0, 0, width, height)); CefBrowserSettings browserSettings; CefBrowserHost::CreateBrowserSync(windowInfo, client.get(), "http://www.google.com", browserSettings, NULL); ``` 这将在Qt应用程序的QWidget中创建一个CEF浏览器。 4. 运行应用程序 现在,您可以运行Qt应用程序并查看CEF浏览器。使用Qt的GUI控件可以与CEF浏览器进行交互,例如在Qt窗口中显示CEF浏览器的网页内容,或在CEF浏览器中嵌入Qt控件。 注意:在集成CEF时,需要考虑QtCEF的线程模型。Qt使用主线程作为GUI线程,而CEF使用多个线程处理浏览器事件和渲染进程事件。因此,在使用CEF时,需要确保在正确的线程上执行GUI操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

paopao_wu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值