基于 CEF 开发桌面应用有其独特的配置要求,比如运行库必须配置为 MTD/MT ,而不能是 MD/MDd 等。
CEF 是 Chromium Embedded Framework 的简写,顾名思义,这是一个把 Chromium 嵌入其他应用的框架。官网地址是:https://bitbucket.org/chromiumembedded/cef,这个开源项目是 Marshall Greenblatt 在 2008 年创立的,由 C/C++ 编写而成,它通过提供稳定的 API 来避免开发者被 Blink、V8、Chromium 等复杂的代码逻辑所困扰。CEF 非常注重开发者的使用体验,很多功能都有默认实现方式,遵从约定优于配置的原则,开发者可以很轻松地驾驭 CEF 框架。
选择 CEF SDK
WIN32
_WINDOWS
__STDC_CONSTANT_MACROS
__STDC_FORMAT_MACROS
_WIN32
UNICODE
_UNICODE
WINVER=0x0601
_WIN32_WINNT=0x601
NOMINMAX
WIN32_LEAN_AND_MEAN
_HAS_EXCEPTIONS=0
PSAPI_VERSION=1
CEF_USE_SANDBOX
CEF_USE_ATL
_HAS_ITERATOR_DEBUGGING=0
_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
E:\project\cef\build\libcef_dll_wrapper\Debug\libcef_dll_wrapper.lib
E:\project\cef\Debug\libcef.lib
E:\project\cef\Debug\cef_sandbox.lib
comctl32.lib
gdi32.lib
rpcrt4.lib
shlwapi.lib
ws2_32.lib
Advapi32.lib
dbghelp.lib
Delayimp.lib
OleAut32.lib
PowrProf.lib
Propsys.lib
psapi.lib
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
application>
compatibility>
assembly>
#include
#include "App.h"
//整个应用的入口函数
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow)
{
CefEnableHighDPISupport();
CefMainArgs main_args(hInstance);
CefSettings settings;
int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
if (exit_code >= 0) {
return exit_code;
}
CefRefPtr<App> app(new App());
CefInitialize(main_args, settings, app.get(), nullptr);
CefRunMessageLoop();
CefShutdown();
return 0;
}
#pragma once
#include "include/cef_app.h"
class App : public CefApp, public CefBrowserProcessHandler
{
public:
App() = default;
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
void OnContextInitialized() override;
private:
IMPLEMENT_REFCOUNTING(App);
};
#ifndef _FileA
#define _FileA
// code
#endif
//App.cpp
#include "App.h"
#include "include/cef_browser.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "WindowDelegate.h"
//CEF主进程上下文环境初始化成功
void App::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
auto url = "https://www.baidu.com";
CefBrowserSettings settings;
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(nullptr, url, settings, nullptr, nullptr, nullptr);
CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
}
// WindowDelegate.h
#pragma once
#include "include/views/cef_window.h"
#include "include/views/cef_browser_view.h"
class WindowDelegate : public CefWindowDelegate
{
public:
explicit WindowDelegate(CefRefPtr<CefBrowserView> browser_view) : browser_view_(browser_view) {};
void OnWindowCreated(CefRefPtr<CefWindow> window) override;
void OnWindowDestroyed(CefRefPtr<CefWindow> window) override;
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override;
WindowDelegate(const WindowDelegate&) = delete;
WindowDelegate& operator=(const WindowDelegate&) = delete;
private:
CefRefPtr<CefBrowserView> browser_view_;
IMPLEMENT_REFCOUNTING(WindowDelegate);
};
//WindowDelegate.cpp
#include "WindowDelegate.h"
#include "include/cef_app.h"
#include "include/views/cef_display.h"
//窗口创建成功
void WindowDelegate::OnWindowCreated(CefRefPtr<CefWindow> window) {
window->AddChildView(browser_view_);
window->Show();
browser_view_->RequestFocus();
window->SetTitle(L"这是我的窗口标题");
//window->CenterWindow(CefSize(800, 600));
}
//窗口销毁成功
void WindowDelegate::OnWindowDestroyed(CefRefPtr<CefWindow> window) {
browser_view_ = nullptr;
CefQuitMessageLoop();
}
//设置窗口位置和大小
CefRect WindowDelegate::GetInitialBounds(CefRefPtr<CefWindow> window) {
CefRefPtr<CefDisplay> display = CefDisplay::GetPrimaryDisplay();
CefRect rect = display->GetBounds();
rect.x = (rect.width - 800) / 2;
rect.y = (rect.height - 600) / 2;
rect.width = 800;
rect.height = 600;
return rect;
}
在这一节我们通过创建一个精简的 CEF 应用程序,来带领你熟悉 CEF 框架的运作机制,可总结为如下:
为了解决加载本地页面的问题, CEF 提供了 CefSchemeHandlerFactory 和 CefResourceHandler 等类型和方法支持用户自定义协议。下面我们就介绍第一个方案,如何使用 CEF 内置的协议加载本地页面。
注册内置协议处理工厂
void App::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
//下面两行代码为新增代码
CefRegisterSchemeHandlerFactory("https", "bread", new HttpSchemeFactory());
std::string url = "https://bread/index.html?a=123";
CefBrowserSettings settings;
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(nullptr, url, settings, nullptr, nullptr, nullptr);
CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
}
//文件名为:HttpSchemeFactory.h
#pragma once
#include "include/cef_app.h"
class HttpSchemeFactory : public CefSchemeHandlerFactory
{
public:
HttpSchemeFactory() = default;
//删除拷贝函数
HttpSchemeFactory(const HttpSchemeFactory&) = delete;
//删除赋值函数
HttpSchemeFactory& operator=(const HttpSchemeFactory&) = delete;
//处理请求的方法定义
CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& scheme_name, CefRefPtr<CefRequest> request) override;
private:
IMPLEMENT_REFCOUNTING(HttpSchemeFactory);
};
//文件名为:HttpSchemeFactory.cpp
#include "HttpSchemeFactory.h"
#include "include/wrapper/cef_helpers.h"
#include "include/wrapper/cef_stream_resource_handler.h"
#include
#include
#include
//处理请求的方法实现
CefRefPtr<CefResourceHandler> HttpSchemeFactory::Create(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& scheme_name,
CefRefPtr<CefRequest> request)
{
CEF_REQUIRE_IO_THREAD();
std::string url = request->GetURL().ToString();
std::string urlPrefix = "https://bread/";
url.erase(0, urlPrefix.size());
size_t paramIndex = url.find_first_of('?');
if (paramIndex != std::string::npos) {
url.erase(paramIndex);
}
TCHAR buffer[MAX_PATH] = { 0 };
GetModuleFileName(NULL, buffer, MAX_PATH);
std::filesystem::path targetPath(buffer);
targetPath = targetPath.parent_path().append("html").append(url);
if (!std::filesystem::exists(targetPath)) {
DLOG(INFO) << L"试图加载:" << targetPath.generic_wstring() << L",但找不到这个文件";
}
std::string ext = targetPath.extension().generic_string();
std::string mime_type_ = ext == ".html"? "text/html":"*";
auto stream = CefStreamReader::CreateForFile(targetPath.generic_wstring());
return new CefStreamResourceHandler(mime_type_, stream);
};
<html>
<head>
<title>第一个本地页面title>
<meta charset="utf-8" />
head>
<body>
这是第一个本地页面<br />
它的地址是:
<script src="/a.js">script>
body>
html>
document.write(location.href);
被 HttpSchemeFactory 工厂类处理的请求除了要匹配 https scheme 之外,还要匹配我们注册的域名: bread 。另外注册的 HTTPS 协议处理工厂只对当前应用生效,不会影响用户操作系统内的其他应用,不用担心应用的兼容性和安全性问题。所以,这个方案适应性还是比较广泛的。
前面介绍了如何使用内置的 HTTPS 协议加载本地页面,实际上所有的内置协议 FTP、 Data 等都可以使用类似的方式加载本地页面。但这也仅仅局限在内置协议的范畴,如果我们要注册一个完全自定义的协议,这种做法就行不通了。本讲中我就带领你注册一个完全自定义的协议。
分离进程处理类
CefMainArgs main_args(hInstance);
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
CefRefPtr<CefApp> app;
if (!command_line->HasSwitch("type")) {
app = new App();
}
else if (command_line->GetSwitchValue("type").ToString() == "renderer") {
app = new Renderer();
}
else {
app = new Other();
}
int exit_code = CefExecuteProcess(main_args, app, nullptr);
void App::OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) {
registrar->AddCustomScheme("my", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED);
}
[scheme]://[username]:[password]@[host]:[port]/[url-path]
my://bread/index.html?a=123
void App::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
CefRegisterSchemeHandlerFactory("my", "bread", new HttpSchemeFactory());
std::string url = "my://bread/index.html?a=123";
CefBrowserSettings settings;
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(nullptr, url, settings, nullptr, nullptr, nullptr);
CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
}
CefRefPtr<CefResourceHandler> CustomSchemeFactory::Create(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& scheme_name,
CefRefPtr<CefRequest> request)
{
CEF_REQUIRE_IO_THREAD();
return new CustomSchemeHandler();
};
// CustomSchemeHandler.h
#pragma once
#include "include/cef_app.h"
#include "include/cef_resource_handler.h"
#include "include/wrapper/cef_helpers.h"
class CustomSchemeHandler : public CefResourceHandler
{
public:
CustomSchemeHandler() : offset_(0) {}
CustomSchemeHandler(const CustomSchemeHandler&) = delete;
CustomSchemeHandler& operator=(const CustomSchemeHandler&) = delete;
//请求发生时触发的事件
bool Open(CefRefPtr<CefRequest> request, bool& handle_request, CefRefPtr<CefCallback> callback) override;
//发送响应头之前触发的事件
void GetResponseHeaders(CefRefPtr<CefResponse> response, int64& response_length, CefString& redirectUrl) override;
//请求被取消时触发的事件
void Cancel() override { CEF_REQUIRE_IO_THREAD(); }
//响应数据时触发的事件
bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefResourceReadCallback> callback) override;
private:
std::string data_;
size_t offset_;
IMPLEMENT_REFCOUNTING(CustomSchemeHandler);
};
// CustomSchemeHandler.cpp
#include "CustomSchemeHandler.h"
#include "include/wrapper/cef_helpers.h"
#include
#include
#include
//请求发生时触发的事件
bool CustomSchemeHandler::Open(CefRefPtr<CefRequest> request, bool& handle_request, CefRefPtr<CefCallback> callback) {
DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO));
handle_request = true;
this->data_ = "这是我自己的页面 这是我自己的内容";
return true;
}
//发送响应头之前触发的事件
void CustomSchemeHandler::GetResponseHeaders(CefRefPtr<CefResponse> response, int64& response_length, CefString& redirectUrl) {
CEF_REQUIRE_IO_THREAD();
DCHECK(!data_.empty());
response->SetMimeType("text/html");
response->SetStatus(200);
response_length = data_.length();
}
//响应数据时触发的事件
bool CustomSchemeHandler::Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefResourceReadCallback> callback) {
DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO));
bool has_data = false;
bytes_read = 0;
if (offset_ < data_.length()) {
int transfer_size = std::min(bytes_to_read, static_cast<int>(data_.length() - offset_));
memcpy(data_out, data_.c_str() + offset_, transfer_size);
offset_ += transfer_size;
bytes_read = transfer_size;
has_data = true;
}
return has_data;
}
<a href="my://bread/index.html?a=123" target="_blank">打开一个新窗口a>
CefRefPtr<ViewDelegate> viewDelegate(new ViewDelegate());
CefRefPtr<CefBrowserView> browserView = CefBrowserView::CreateBrowserView(pageHandler, url, settings, nullptr, nullptr, viewDelegate);
#pragma once
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
class ViewDelegate : public CefBrowserViewDelegate
{
public:
ViewDelegate() = default;
ViewDelegate(const ViewDelegate&) = delete;
ViewDelegate& operator=(const ViewDelegate&) = delete;
// 当前页面弹出新窗口时此方法被执行
bool OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browser_view, CefRefPtr<CefBrowserView> popup_browser_view, bool is_devtools) override;
private:
IMPLEMENT_REFCOUNTING(ViewDelegate);
};
#include "ViewDelegate.h"
#include "WindowDelegate.h"
// 当前页面弹出新窗口时此方法被执行
bool ViewDelegate::OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browserView, CefRefPtr<CefBrowserView> popupBrowserView, bool isDevtools)
{
CefWindow::CreateTopLevelWindow(new WindowDelegate(popupBrowserView));
return true;
}
CefRefPtr<PageHandler> pageHandler(new PageHandler());
CefRefPtr<ViewDelegate> viewDelegate(new ViewDelegate());
CefRefPtr<CefBrowserView> browserView = CefBrowserView::CreateBrowserView(pageHandler, url, settings, nullptr, nullptr, viewDelegate);
#pragma once
#include "include/cef_app.h"
#include
class PageHandler : public CefClient, public CefLifeSpanHandler
{
public:
PageHandler() = default;
//获取LifeSpanHandler对象
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
//页面创建成功
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
//页面即将关闭
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;
private:
IMPLEMENT_REFCOUNTING(PageHandler);
std::list<CefRefPtr<CefBrowser>> browsers;
};
#include "PageHandler.h"
#include "include/wrapper/cef_helpers.h"
//页面创建成功
void PageHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
browsers.push_back(browser);
}
//页面即将关闭
void PageHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
std::list<CefRefPtr<CefBrowser>>::iterator bit = browsers.begin();
for (; bit != browsers.end(); ++bit) {
if ((*bit)->IsSame(browser)) {
browsers.erase(bit);
break;
}
}
if (browsers.empty()) {
CefQuitMessageLoop();
}
}
window.onbeforeunload = function(){
return "askForClose"
}
bool CanClose(CefRefPtr<CefWindow> window) override;
bool WindowDelegate::CanClose(CefRefPtr window) {
bool result = true;
CefRefPtr browser = browserView->GetBrowser();
if (browser) {
result = browser->GetHost()->TryCloseBrowser();
}
return result;
}
// 现在 PageHandler 类的继承关系如下:
// class PageHandler : public CefClient, public CefLifeSpanHandler, public CefJSDialogHandler
CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() override { return this; }
bool OnBeforeUnloadDialog(CefRefPtr<CefBrowser> browser, const CefString& message_text, bool is_reload, CefRefPtr<CefJSDialogCallback> callback) override;
bool PageHandler::OnBeforeUnloadDialog(CefRefPtr<CefBrowser> browser, const CefString& message_text, bool is_reload, CefRefPtr<CefJSDialogCallback> callback) {
HWND hwnd = browser->GetHost()->GetWindowHandle();
int msgboxID = MessageBox(hwnd, L"您编辑的内容尚未保存.\n确定要关闭窗口吗?", L"系统提示", MB_ICONEXCLAMATION | MB_OKCANCEL);
if (msgboxID == IDOK) {
callback->Continue(true, CefString());
}
else {
callback->Continue(false, CefString());
}
return true;
}
//头文件中要加入对应的方法声明
//bool OnJSDialog(CefRefPtr browser, const CefString& origin_url,JSDialogType dialog_type,const CefString& message_text,const CefString& default_prompt_text,CefRefPtr callback,bool& suppress_message) override;
bool PageHandler::OnJSDialog(CefRefPtr<CefBrowser> browser,
const CefString& origin_url,
JSDialogType dialog_type,
const CefString& message_text,
const CefString& default_prompt_text,
CefRefPtr<CefJSDialogCallback> callback,
bool& suppress_message) {
suppress_message = false;
HWND hwnd = browser->GetHost()->GetWindowHandle();
if (dialog_type == JSDialogType::JSDIALOGTYPE_ALERT) {
MessageBox(hwnd, message_text.c_str(), L"系统提示", MB_ICONEXCLAMATION | MB_OK);
callback->Continue(true, CefString());
}
else if(dialog_type == JSDialogType::JSDIALOGTYPE_CONFIRM){
int msgboxID = MessageBox(hwnd, message_text.c_str(), L"系统提示", MB_ICONEXCLAMATION | MB_YESNO);
callback->Continue(msgboxID == IDYES, CefString());
}
else if (dialog_type == JSDialogType::JSDIALOGTYPE_PROMPT){
//这部分逻辑稍后讲解
}
return true;
}
typedef enum {
JSDIALOGTYPE_ALERT = 0, //alert弹窗
JSDIALOGTYPE_CONFIRM, //confirm弹窗
JSDIALOGTYPE_PROMPT, //prompt弹窗
} cef_jsdialog_type_t;
let flag = confirm("这是一个confirm对话框 \n做出选择之后我会把你的选择alert出来");
alert("用户选择了:" + (flag?"确认":"取消"));
prompt 对话框不能再通过 MessageBox 定义,因为 prompt 对话框是一个请求用户输入的对话框,这个对话框内包含一个文本输入框,而 MessageBox 创建的任何样式的对话框都没有文本输入框,所以只能自己设计并实现这么一个对话框。
自定义对话框
//对话框ID
#define IDD_PROMPT 101
//文本框ID
#define IDC_INPUT 1001
//提示信息显示控件的ID
#define IDC_LABEL 1002
DialogBox(hinst, MAKEINTRESOURCE(IDD_PROMPT), hwnd, (DLGPROC)PromptProc)
// 这段代码被放置在 PageHandler.cpp 文件中
// 要使用自定义对话框资源必须提前 #include "resource.h"
namespace{
LPWSTR infoValue;
LPWSTR userInputValue;
LPWSTR defaultValue;
BOOL CALLBACK PromptProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) {
BOOL result = FALSE;
switch (message) {
case WM_INITDIALOG:{ //对话框创建成功的消息
SetDlgItemText(hwndDlg, IDC_LABEL, infoValue); //设置提示信息控件的文本信息
SetDlgItemText(hwndDlg, IDC_INPUT, defaultValue); //设置文本输入框的默认文本信息
HWND hwndInput = GetDlgItem(hwndDlg, IDC_INPUT); //得到文本输入框的窗口句柄
SetFocus(hwndInput); //把鼠标光标聚焦在文本输入框内
SendMessage(hwndInput, EM_SETSEL, 0, -1); //发送选中文本输入框内的所有内容的消息
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDOK: { //用户点击确定按钮的消息
HWND hwndInput = GetDlgItem(hwndDlg, IDC_INPUT);
int outLength = GetWindowTextLength(hwndInput) + 1;
userInputValue = new TCHAR[outLength];
GetDlgItemText(hwndDlg, IDC_INPUT, userInputValue, outLength);
EndDialog(hwndDlg, wParam);
result = TRUE;
break;
}
case IDCANCEL: { //用户点击取消按钮的消息
EndDialog(hwndDlg, wParam);
result = FALSE;
break;
}
}
break;
}
}
return result;
}
}
// 之前的逻辑在上一节中已经详细讲述了
else if (dialog_type == JSDialogType::JSDIALOGTYPE_PROMPT){
HINSTANCE hinst = GetModuleHandle(NULL);
defaultValue = (LPWSTR)default_prompt_text.c_str();
infoValue = (LPWSTR)message_text.c_str();
BOOL result = DialogBox(hinst, MAKEINTRESOURCE(IDD_PROMPT), hwnd, (DLGPROC)PromptProc);
if (result == 1) {
callback->Continue(result, CefString(userInputValue));
delete []userInputValue;
}
else{
callback->Continue(result, CefString());
}
}
let userInput = prompt("请您输入一段文字", "这是一段文字的默认值");
alert("用户输入了:" + userInput);
//PageHandler类的集成关系目前为:
//class PageHandler:public CefClient, public CefLifeSpanHandler, public CefJSDialogHandler, public CefContextMenuHandler
CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() override { return this; }
virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, CefRefPtr<CefMenuModel> model) override;
virtual bool OnContextMenuCommand(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, int command_id, EventFlags event_flags) override;
void PageHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, CefRefPtr<CefMenuModel> model)
{
model->Clear();
model->AddItem(MENU_ID_USER_FIRST, L"打开开发者调试工具");
CefRefPtr<CefMenuModel> subModel = model->AddSubMenu(MENU_ID_USER_FIRST + 1, L"这是一个包含子菜单的测试菜单");
subModel->AddItem(MENU_ID_USER_FIRST + 2, L"这是子菜单1");
subModel->AddItem(MENU_ID_USER_FIRST + 3, L"这是子菜单2");
model->AddSeparator();
model->AddCheckItem(MENU_ID_USER_FIRST + 4, L"这是一个包含复选框的菜单");
model->SetChecked(MENU_ID_USER_FIRST + 4,true);
model->AddRadioItem(MENU_ID_USER_FIRST + 5, L"这是一个包含复选框的菜单",888);
model->AddRadioItem(MENU_ID_USER_FIRST + 6, L"这是一个包含单选框的菜单", 888);
model->SetChecked(MENU_ID_USER_FIRST + 6, true);
}
bool PageHandler::OnContextMenuCommand(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefContextMenuParams> params, int command_id, EventFlags event_flags)
{
switch (command_id)
{
case MENU_ID_USER_FIRST: {
//这部分内容稍后讲解
}
default: {
std::wstring msg = L"你点击的标签ID:" + std::to_wstring(command_id);
MessageBox(NULL, (LPWSTR)msg.c_str(), L"系统提示", MB_ICONEXCLAMATION | MB_OKCANCEL);
break;
}
}
return true;
}
case MENU_ID_USER_FIRST: {
CefBrowserSettings browserSettings;
CefWindowInfo windowInfo;
CefPoint mousePoint(params->GetXCoord(), params->GetYCoord());
browser->GetHost()->ShowDevTools(windowInfo, this, browserSettings, mousePoint);
break;
}
自定义的标题栏效果都是通过屏蔽掉系统默认标题栏,然后在一个无标题栏的窗口内实现一个自定义的标题栏实现的。
无边框窗口
#pragma once
#include "include/views/cef_window.h"
#include "include/views/cef_browser_view.h"
class WindowDelegate : public CefWindowDelegate
{
public:
//构造函数增加了一个参数,用于初始化isDevTool
explicit WindowDelegate(CefRefPtr<CefBrowserView> browser_view,bool dev_tool) : browserView(browser_view),isDevTool(dev_tool) {};
// 无边框窗口,屏蔽掉系统默认的标题栏
bool IsFrameless(CefRefPtr<CefWindow> window) override;
void OnWindowCreated(CefRefPtr<CefWindow> window) override;
void OnWindowDestroyed(CefRefPtr<CefWindow> window) override;
bool CanClose(CefRefPtr<CefWindow> window) override;
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override;
WindowDelegate(const WindowDelegate&) = delete;
WindowDelegate& operator=(const WindowDelegate&) = delete;
private:
// 当前窗口是否为开发者工具窗口
bool isDevTool;
CefRefPtr<CefBrowserView> browserView;
IMPLEMENT_REFCOUNTING(WindowDelegate);
};
CefWindow::CreateTopLevelWindow(new WindowDelegate(browserView,false));
bool ViewDelegate::OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browserView, CefRefPtr<CefBrowserView> popupBrowserView, bool isDevtools)
{
CefWindow::CreateTopLevelWindow(new WindowDelegate(popupBrowserView,isDevtools));
return true;
}
bool WindowDelegate::IsFrameless(CefRefPtr<CefWindow> window) {
if (isDevTool) {
return false;
}
return true;
}
<html>
<head>
<title>第一个本地页面title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="icon/iconfont.css" />
<style>
/*CSS代码放在此处,稍后会介绍*/
style>
head>
<body>
<div class="titleBar">
<div class="titleContent">这是我的第一个窗口div>
<div class="toolBox">
<div id="minimizeBtn"> <i class="icon icon-minimize">i> div>
<div id="maximizeBtn"> <i class="icon icon-maximize">i> div>
<div id="restoreBtn" style="display:none"> <i class="icon icon-restore">i> div>
<div id="closeBtn"> <i class="icon icon-close">i> div>
div>
div>
<div class="content">
这是窗口的内容区域<br />
<a href="my://bread/index.html?a=123" target="_blank">打开一个新窗口a>
div>
body>
html>
html, body {
margin: 0px;
padding: 0px;
height: 100%;
width: 100%;
}
body {
user-select: none;
font-size: 13px;
color: #333;
font-family: -apple-system, 'Microsoft Yahei', Ubuntu, sans-serif;
display: flex;
flex-direction: column;
}
.titleBar {
height: 38px;
background: #ffd6e7;
line-height: 38px;
padding-left: 16px;
display: flex;
}
.titleContent {
flex: 1;
-webkit-app-region: drag;
}
.toolBox {
width: 180px;
display:flex;
}
.toolBox div{
flex:1;
text-align:center;
}
.toolBox div:hover {
background: #ffadd2;
}
#closeBtn:hover {
background: #ff7875;
color:#fff;
}
.toolBox div i {
font-size: 12px;
}
.content {
flex: 1;
text-align: center;
padding-top: 120px;
}
// PageHandler 类的继承关系如下:
//class PageHandler : public CefClient, public CefLifeSpanHandler, public CefJSDialogHandler, public CefContextMenuHandler, public CefDragHandler
CefRefPtr<CefDragHandler> GetDragHandler() override { return this; }
virtual void OnDraggableRegionsChanged(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion>& regions) override;
void PageHandler::OnDraggableRegionsChanged(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion>& regions)
{
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::GetForBrowser(browser);
if (browser_view)
{
CefRefPtr<CefWindow> window = browser_view->GetWindow();
if (window) window->SetDraggableRegions(regions);
}
}
// 这个成员方法是public类型的
void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) override;
// 这个成员变量是private类型的
CefRefPtr<V8Handler> v8Handler;
void Renderer::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) {
CefRefPtr<CefV8Value> globalObject = context->GetGlobal();
v8Handler = new V8Handler();
CefRefPtr<CefV8Value> nativeCall = CefV8Value::CreateFunction("nativeCall", v8Handler);
globalObject->SetValue("nativeCall", nativeCall, V8_PROPERTY_ATTRIBUTE_READONLY);
}
#pragma once
#include "include/cef_v8.h"
class V8Handler : public CefV8Handler
{
public:
V8Handler() = default;
virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) override;
private:
IMPLEMENT_REFCOUNTING(V8Handler);
};
#include "V8Handler.h"
bool V8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
{
auto msgName = arguments[0]->GetStringValue();
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName);
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
context.get()->GetFrame()->SendProcessMessage(PID_BROWSER, msg);
return true;
};
> window.nativeCall
< ƒ nativeCall() { [native code] }
let browserWindow = {
getMsgName(args) {
return `window_${args.callee.name}`
},
minimize() {
let msgName = this.getMsgName(arguments);
window.nativeCall(msgName);
},
maximize() {
let msgName = this.getMsgName(arguments);
window.nativeCall(msgName);
},
close() {
let msgName = this.getMsgName(arguments);
window.nativeCall(msgName);
},
restore() {
let msgName = this.getMsgName(arguments);
window.nativeCall(msgName);
},
}
let minimizeBtn = document.querySelector("#minimizeBtn");
let maximizeBtn = document.querySelector("#maximizeBtn");
let restoreBtn = document.querySelector("#restoreBtn");
let closeBtn = document.querySelector("#closeBtn");
minimizeBtn.addEventListener("click", () => browserWindow.minimize());
closeBtn.addEventListener("click", () => browserWindow.close())
maximizeBtn.addEventListener("click", () => {
browserWindow.maximize();
maximizeBtn.setAttribute("style", "display:none");
restoreBtn.removeAttribute("style");
})
restoreBtn.addEventListener("click", () => {
browserWindow.restore();
restoreBtn.setAttribute("style", "display:none");
maximizeBtn.removeAttribute("style");
})
我们为窗口标题栏的按钮添加了点击事件的处理逻辑,而且在执行这些点击事件的时候,我们成功的通过 JavaScript 代码调用了在渲染进程中实现的 C++ 代码。但这还远远不够,用户点击这些标题栏按钮的时候,窗口状态仍旧没有变化,这是因为我们虽然把控制消息发送给了浏览器进程,但还没有为浏览器进程撰写接收消息和处理消息的逻辑。
主进程接收并处理渲染进程消息
bool OnProcessMessageReceived(CefRefPtr browser, CefRefPtr frame, CefProcessId source_process, CefRefPtr message) override;
bool PageHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
CEF_REQUIRE_UI_THREAD();
std::string messageName = message->GetName();
std::vector<std::string> arr = split(messageName, '_');
if (arr.at(0) == "window"){
CefRefPtr<CefBrowserView> browserView = CefBrowserView::GetForBrowser(browser);
CefRefPtr<CefWindow> window = browserView->GetWindow();
if (arr.at(1) == "minimize") {
window->Minimize();
}
else if (arr.at(1) == "maximize") {
window->Maximize();
}
else if (arr.at(1) == "close") {
window->Close();
}
else if (arr.at(1) == "restore") {
window->Restore();
}
}
return true;
}
std::vector<std::string> split(const std::string& s, char delim) {
std::vector<std::string> elems;
std::istringstream iss(s);
std::string item;
while (std::getline(iss, item, delim)) {
elems.push_back(item);
}
return elems;
}
let eventer = {
//事件容器
dic: {},
//监听事件
on(eventName, callBack) {
if (!this.dic[eventName]) this.dic[eventName] = [callBack]
else this.dic[eventName].push(callBack)
},
//发射事件
emit(eventName,...obj) {
if (!this.dic[eventName]) {
console.warn(`没有找到该事件的监听函数:${eventName}`)
return;
}
this.dic[eventName].forEach((func) => func(...obj))
},
//取消监听事件
off(eventName, callBack) {
if (!this.dic[eventName]) return
if (!callBack) {
delete this.dic[eventName]
return
}
let index = this.dic[eventName].findIndex((v) => v == callBack)
if (index >= 0) this.dic[eventName].splice(index, 1)
if (this.dic[eventName].length < 1) delete this.dic[eventName];
}
//监听一次性事件
once(eventName, callBack) {
let callBackWrap = (...obj) => {
let index = this.dic[eventName].findIndex((v) => v == callBackWrap)
if (index >= 0) this.dic[eventName].splice(index, 1)
if (this.dic[eventName].length < 1) delete this.dic[eventName];
callBack(...obj)
}
if (!this.dic[eventName]) this.dic[eventName] = [callBackWrap]
else this.dic[eventName].push(callBackWrap)
},
}
eventer.on("window_maximize", () => {
maximizeBtn.setAttribute("style", "display:none");
restoreBtn.removeAttribute("style");
})
eventer.on("window_unMaximize", () => {
restoreBtn.setAttribute("style", "display:none");
maximizeBtn.removeAttribute("style");
})
maximized: false,
isMaximized() {
let hSpan = window.outerHeight - screen.availHeight
let wSpan = window.outerWidth - screen.availWidth
return Math.abs(hSpan) < 2 && Math.abs(wSpan) < 2
},
init() {
window.addEventListener('resize', () => {
let curState = this.isMaximized()
let oldState = this.maximized
this.maximized = curState
if (oldState && !curState) eventer.emit(`window_unMaximize`)
else if (!oldState && curState) eventer.emit(`window_maximize`)
})
}
let native = {
randomNum(len = 12) {
return Math.floor(Math.pow(10, len) * Math.random());
},
call(msgName, ...params) {
return new Promise((resolve, reject) => {
let eventName = `${msgName}_${this.randomNum()}`;
eventer.once(eventName, (obj) => {
resolve(obj);
});
window.nativeCall(eventName, ...params);
});
},
init() {
window.nativeCall(`native_registe_callback`, (msgName, ...otherParams) => {
eventer.emit(msgName, ...otherParams);
});
},
};
native.init();
let system = {
getMsgName(args) {
return `system_${args.callee.name}`;
},
async getOSVersion() {
let msgName = this.getMsgName(arguments);
let osVersion = await native.call(msgName);
return osVersion;
},
};
bool V8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
{
auto msgName = arguments[0]->GetStringValue();
if (msgName == "native_registe_callback") {
callBack = arguments[1];
return true;
}
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName);
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
context.get()->GetFrame()->SendProcessMessage(PID_BROWSER, msg);
return true;
};
public:
CefRefPtr<CefV8Value> callBack;
v8Handler->callBack->ExecuteFunction(nullptr, obj);
else if(arr.at(0) == "system") {
// messageName是在进入OnProcessMessageReceived方法时就通过如下代码获得了,它就是渲染进程发来的消息名
// std::string messageName = message->GetName();
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(messageName);
CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
if (arr.at(1) == "getOSVersion") {
std::string version = getOSVersion();
msgArgs->SetString(0, version);
}
frame->SendProcessMessage(PID_RENDERER, msg);
}
const std::string getOSVersion()
{
NTSTATUS(WINAPI* RtlGetVersion)(LPOSVERSIONINFOEXW);
OSVERSIONINFOEXW osInfo;
std::string result;
*(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandle(L"ntdll"), "RtlGetVersion");
if (nullptr != RtlGetVersion) {
osInfo.dwOSVersionInfoSize = sizeof osInfo;
RtlGetVersion(&osInfo);
result = std::to_string(osInfo.dwMajorVersion) +"." + std::to_string(osInfo.dwMinorVersion) +"." + std::to_string(osInfo.dwBuildNumber);
}
return result;
}
bool Renderer::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
CefString messageName = message->GetName();
CefRefPtr<CefListValue> args = message->GetArgumentList();
CefString result = args->GetString(0);
CefRefPtr<CefV8Value> messageNameV8 = CefV8Value::CreateString(messageName);
CefRefPtr<CefV8Value> resultV8 = CefV8Value::CreateString(result);
CefV8ValueList argsForJs;
argsForJs.push_back(messageNameV8);
argsForJs.push_back(resultV8);
CefRefPtr<CefV8Context> context = frame->GetV8Context();
context->Enter();
v8Handler->callBack->ExecuteFunction(nullptr, argsForJs);
context->Exit();
return true;
}
let dialog = {
getMsgName(args) {
return `dialog_${args.callee.name}`;
},
async openFile(param) {
let msgName = this.getMsgName(arguments);
let resultStr = await native.call(msgName, JSON.stringify(param));
return JSON.parse(resultStr);
},
async openFolder(param) {
let msgName = this.getMsgName(arguments);
let resultStr = await native.call(msgName, JSON.stringify(param));
return JSON.parse(resultStr);
},
};
let fileOpenBtn = document.querySelector("#fileOpenBtn");
fileOpenBtn.addEventListener("click", async () => {
let param = {
title: "这是打开文件对话框的标题",
defaultPath: "C:\\Program Files",
filters: ["image/*", "text/*"],
filterIndex: 1,
multiSelections: true,
};
let files = await dialog.openFile(param);
console.log(files);
});
let dirOpneBtn = document.querySelector("#dirOpneBtn");
dirOpneBtn.addEventListener("click", async () => {
let param = {
title: "这是打开文件夹对话框的标题",
defaultPath: "C:\\Program Files",
};
let files = await dialog.openFolder(param);
console.log(files);
});
bool V8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
{
auto msgName = arguments[0]->GetStringValue();
if (msgName == "native_registe_callback") {
callBack = arguments[1];
return true;
}
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName);
CefRefPtr<CefListValue> msgBody = msg->GetArgumentList();
if (arguments.size() > 1 && arguments[1]->IsString()) {
msgBody->SetString(0, arguments[1]->GetStringValue());
}
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
context.get()->GetFrame()->SendProcessMessage(PID_BROWSER, msg);
return true;
};
else if (arr.at(0) == "dialog") {
CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
std::wstring title = convertStr(param["title"].get<std::string>());
std::wstring defaultPath = convertStr(param["defaultPath"].get<std::string>());
CefRefPtr<CefRunFileDialogCallback> dcb = new DialogHandler(messageName, frame);
CefBrowserHost::FileDialogMode mode;
std::vector<CefString> fileFilters;
int filterIndex = 0;
if (arr.at(1) == "openFile") {
for (const std::string& var : param["filters"]) {
fileFilters.push_back(var);
}
filterIndex = param["filterIndex"].get<int>();
mode = param["multiSelections"].get<bool>() ? FILE_DIALOG_OPEN_MULTIPLE : FILE_DIALOG_OPEN;
browser->GetHost()->RunFileDialog(mode, title, defaultPath, fileFilters, filterIndex, dcb);
}
else if (arr.at(1) == "openFolder") {
mode = FILE_DIALOG_OPEN_FOLDER;
browser->GetHost()->RunFileDialog(mode, title, defaultPath, fileFilters, filterIndex, dcb);
}
}
std::wstring convertStr(const std::string& str)
{
static std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
return utf8_conv.from_bytes(str);
}
#pragma once
#include "include/wrapper/cef_message_router.h"
#include "include/cef_browser.h"
#include "Helper/json.hpp"
using nlohmann::json;
class DialogHandler : public CefRunFileDialogCallback
{
public:
DialogHandler(std::string& msgName, CefRefPtr<CefFrame> frame) :msgName(msgName), frame(frame) {};
void OnFileDialogDismissed(int selected_accept_filter, const std::vector<CefString>& file_paths) override {
json result;
result["success"] = true;
result["data"] = {};
for (size_t i = 0; i < file_paths.size(); i++)
{
result["data"].push_back(file_paths[i].ToString());
}
CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName);
CefRefPtr<CefListValue> msgArgs = msgBack->GetArgumentList();
std::string dataStr = result.dump();
msgArgs->SetString(0, dataStr);
frame->SendProcessMessage(PID_RENDERER, msgBack);
}
DialogHandler(const DialogHandler&) = delete;
DialogHandler& operator=(const DialogHandler&) = delete;
private:
std::string msgName;
CefRefPtr<CefFrame> frame;
IMPLEMENT_REFCOUNTING(DialogHandler);
};
let file = {
getMsgName(args) {
return `file_${args.callee.name}_${this.randomNum()}`;
},
randomNum(len = 12) {
return Math.floor(Math.pow(10, len) * Math.random());
},
readFile(param) {
let msgName = this.getMsgName(arguments);
let onData = (obj) => {
if (param.onData) {
param.onData(obj);
}
};
let onFinish = (obj) => {
if (param.onFinish) {
param.onFinish(obj);
}
eventer.off(`${msgName}_data`, onData);
};
eventer.on(`${msgName}_data`, onData);
eventer.once(`${msgName}_finish`, onFinish);
window.nativeCall(msgName, JSON.stringify(param));
},
};
let readFileBtn = document.querySelector("#readFileBtn");
readFileBtn.addEventListener("click", async () => {
let result = "";
let param = {
filePath: "E:\\project\\bread\\14.md",
onData(chunk) {
let decoder = new TextDecoder("utf-8", { ignoreBOM: true });
let str = decoder.decode(chunk);
result += str;
},
onFinish(data) {
console.log("文件读取完成");
console.log(result);
},
};
file.readFile(param);
});
// 需要引入如下两个头文件
// #include "include/cef_task.h"
// #include "include/wrapper/cef_closure_task.h"
else if (arr.at(0) == "file") {
if (arr.at(1) == "readFile") {
CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
std::string filePath = param["filePath"].get<std::string>();
CefPostTask(TID_FILE_BACKGROUND, base::BindOnce(&ReadFileByBlocks, filePath,messageName, frame));
}
}
// 需要引入如下两个标准库
// #include
// #include
void ReadFileByBlocks( const std::string& filePath,const std::string& msgName, CefRefPtr<CefFrame> frame) {
std::ifstream fileStream(filePath, std::ifstream::binary);
if (fileStream) {
fileStream.seekg(0, fileStream.end);
long length = fileStream.tellg();
fileStream.seekg(0, fileStream.beg);
long size = 1024;
long position = 0;
while (position != length) {
long leftSize = length - position;
if (leftSize < size) {
size = leftSize;
}
char* buffer = new char[size];
fileStream.read(buffer, size);
position = fileStream.tellg();
CefRefPtr<CefBinaryValue> data = CefBinaryValue::Create(buffer, size);
CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName+"_data");
CefRefPtr<CefListValue> msgArgs = msgBack->GetArgumentList();
msgArgs->SetBinary(0, data);
frame->SendProcessMessage(PID_RENDERER, msgBack);
delete[] buffer;
}
fileStream.close();
CefRefPtr<CefProcessMessage> msgBack = CefProcessMessage::Create(msgName + "_finish");
frame->SendProcessMessage(PID_RENDERER, msgBack);
}
}
bool Renderer::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
CefString messageName = message->GetName();
CefRefPtr<CefListValue> args = message->GetArgumentList();
CefRefPtr<CefV8Context> context = frame->GetV8Context();
context->Enter();
CefRefPtr<CefV8Value> messageNameV8 = CefV8Value::CreateString(messageName);
CefV8ValueList argsForJs;
argsForJs.push_back(messageNameV8);
if (args->GetType(0) == CefValueType::VTYPE_STRING) {
CefString result = args->GetString(0);
CefRefPtr<CefV8Value> resultV8 = CefV8Value::CreateString(result);
argsForJs.push_back(resultV8);
} //下面else if分支是我们增加的内容
else if (args->GetType(0) == CefValueType::VTYPE_BINARY) {
CefRefPtr<CefBinaryValue> data = args->GetBinary(0);
size_t size = data->GetSize();
unsigned char* result = new unsigned char[size];
data->GetData(result, size, 0);
CefRefPtr<CefV8ArrayBufferReleaseCallback> cb = new ReleaseCallback();
CefRefPtr<CefV8Value> resultV8 = CefV8Value::CreateArrayBuffer(result, size, cb);
argsForJs.push_back(resultV8);
}
v8Handler->callBack->ExecuteFunction(nullptr, argsForJs);
context->Exit();
return true;
}
#pragma once
#include "V8Handler.h"
class ReleaseCallback : public CefV8ArrayBufferReleaseCallback {
public:
void ReleaseBuffer(void* buffer) override {
std::free(buffer);
}
IMPLEMENT_REFCOUNTING(ReleaseCallback);
};
let db = {
getMsgName(args) {
return `db_${args.callee.name}`;
},
async open(param) {
let msgName = this.getMsgName(arguments);
let result = await native.call(msgName, JSON.stringify(param));
return JSON.parse(result);
},
async close() {
let msgName = this.getMsgName(arguments);
let result = await native.call(msgName);
return JSON.parse(result);
},
async execute(param) {
let msgName = this.getMsgName(arguments);
let result = await native.call(msgName, JSON.stringify(param));
return JSON.parse(result);
},
};
let dbBtn = document.querySelector("#dbBtn");
dbBtn.addEventListener("click", async () => {
if (dbBtn.innerHTML === "打开数据库") {
let result = await db.open({
dbPath: "E:\\project\\cef-in-action\\test.db",
});
console.log(result);
dbBtn.innerHTML = "关闭数据库";
} else if (dbBtn.innerHTML === "关闭数据库") {
let result = await db.close();
console.log(result);
dbBtn.innerHTML = "打开数据库";
}
});
CREATE TABLE Message(Message TEXT NOT NULL, fromUser CHAR(60) NOT NULL, toUser CHAR(60) NOT NULL, sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
let insertBtn = document.querySelector("#insertBtn");
insertBtn.addEventListener("click", async () => {
//
let msgs = [
{
message: `天接云涛连晓雾`,
fromUser: `李清照`,
toUser: "辛弃疾",
},
{
message: `醉里挑灯看剑`,
fromUser: `辛弃疾`,
toUser: "李清照",
},
];
let sqls = [];
for (let i = 0; i < 60; i++) {
let msg = msgs[i % 2];
sqls.push(
`insert into Message(Message, fromUser, toUser) values ('${msg.message}','${msg.fromUser}','${msg.toUser}');`
);
}
let result = await db.execute({ sql: sqls.join("") });
console.log(result);
});
let selectBtn = document.querySelector("#selectBtn");
selectBtn.addEventListener("click", async () => {
let sql = `select rowid,* from Message limit 16;`;
let result = await db.execute({ sql });
console.log(result);
});
let updateBtn = document.querySelector("#updateBtn");
updateBtn.addEventListener("click", async () => {
let obj = {
message:
"怒发冲冠",
fromUser: "岳飞",
toUser: "辛弃疾",
};
let sql = `update Message set Message = '${obj.message}',fromUser = '${obj.fromUser}',toUser='${obj.toUser}' where rowid in (select rowid from Message limit 1);`;
let result = await db.execute({ sql });
console.log(result);
});
let deleteBtn = document.querySelector("#deleteBtn");
deleteBtn.addEventListener("click", async () => {
let sql = `delete from Message where rowid in (select rowid from Message limit 1);`;
let result = await db.execute({ sql });
console.log(result);
});
else if (arr.at(0) == "db") {
CefRefPtr<CefListValue> msgBody = message->GetArgumentList();
if (arr.at(1) == "open") {
nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
std::string dbPath = param["dbPath"].get<std::string>();
int rc = sqlite3_open(dbPath.c_str(), &db);
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(messageName);
CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
json result;
result["success"] = rc == 0;
msgArgs->SetString(0, result.dump());
frame->SendProcessMessage(PID_RENDERER, msg);
}
else if(arr.at(1) == "close") {
int rc = sqlite3_close(db);
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(messageName);
CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
json result;
result["success"] = rc == 0;
msgArgs->SetString(0, result.dump());
frame->SendProcessMessage(PID_RENDERER, msg);
}
else if (arr.at(1) == "execute") {
nlohmann::json param = nlohmann::json::parse(msgBody->GetString(0).ToString());
std::string sqlStr = param["sql"].get<std::string>();
CefPostTask(TID_FILE_BACKGROUND, base::BindOnce(&ExecuteSql, sqlStr, messageName, frame));
}
}
void ExecuteSql(const std::string& sqlStr, const std::string& msgName, CefRefPtr<CefFrame> frame) {
json result;
result["data"] = {};
const char* zTail = sqlStr.c_str();
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
while (strlen(zTail) != 0) {
sqlite3_stmt* stmt = NULL;
//判断 prepareResult == SQLITE_OK并处理异常
int prepareResult = sqlite3_prepare_v2(db, zTail, -1, &stmt, &zTail);
int stepResult = sqlite3_step(stmt);
while (stepResult == SQLITE_ROW) {
json row;
int columnCount = sqlite3_column_count(stmt);
for (size_t i = 0; i < columnCount; i++) {
std::string columnName = sqlite3_column_name(stmt, i);
int type = sqlite3_column_type(stmt, i);
if (type == SQLITE_INTEGER) {
row[columnName] = sqlite3_column_int(stmt, i);
}
else if (type == SQLITE3_TEXT)
{
const unsigned char* val = sqlite3_column_text(stmt, i);
row[columnName] = reinterpret_cast<const char*>(val);
}
//这里只处理了两种数据类型是不足以满足生产条件的
}
result["data"].push_back(row);
stepResult = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
}
sqlite3_exec(db, "END TRANSACTION;", NULL, NULL, NULL);
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName);
CefRefPtr<CefListValue> msgArgs = msg->GetArgumentList();
result["success"] = true;
msgArgs->SetString(0, result.dump());
frame->SendProcessMessage(PID_RENDERER, msg);
}
WIN32
_WINDOWS
NDEBUG
%(PreprocessorDefinitions)
__STDC_CONSTANT_MACROS
__STDC_FORMAT_MACROS
_WIN32
UNICODE
_UNICODE
WINVER=0x0601
_WIN32_WINNT=0x601
NOMINMAX
WIN32_LEAN_AND_MEAN
_HAS_EXCEPTIONS=0
PSAPI_VERSION=1
CEF_USE_SANDBOX
CEF_USE_ATL
_NDEBUG
CMAKE_INTDIR="Release"