三、窗口控制
- 新窗口没有大小
- 如果在我的第一个页面中,增加一个 target 为 _blank 的链接(只有把 target 属性设置为 _blank 时,用户点击链接才会打开新窗口),如下代码所示:
<a href="my://bread/index.html?a=123" target="_blank">打开一个新窗口</a>- 注意,这里我们尝试打开的是我们自定义协议所指向的页面地址,而且这个地址就是当前页面的地址,也就是说打开的新窗口加载的也是当前页面,而且在新窗口也可以通过点击这个连接打开另一个新窗口。
- 运行程序,点击这个连接,虽然可以打开一个新窗口,但这个窗口却没有尺寸。
- 实际上,如果我们没有为第一个窗口提供 WindowDelegate 对象的话,我们的第一个窗口也会是这样的,但第一个窗口的 WindowDelegate 对象是我们在 CreateTopLevelWindow 时手动附加上去的,那么类似这种用户自己打开的窗口我们该如何动态把 WindowDelegate 对象附加上去呢?
- 用户打开新窗口这个事件是发生在 ViewDelegate 对象上的,我们在前面的示例中并没有定义这个对象,所以就没有机会把 WindowDelegate 附加到新窗口上去。接下来我们就定义一下这个对象。
- 创建新窗口的事件
- 这个 ViewDelegate 对象是在 App 类的 OnContextInitialized 方法内创建的,如下代码所示:
CefRefPtr<ViewDelegate> viewDelegate(new ViewDelegate()); CefRefPtr<CefBrowserView> browserView = CefBrowserView::CreateBrowserView(pageHandler, url, settings, nullptr, nullptr, viewDelegate); - ViewDelegate 是我们自定义的一个类型,它继承自 CefBrowserViewDelegate 类,我们通过 CreateBrowserView 方法的最后一个参数(之前这个参数传递的是 nullptr )把这个对象传递给 CEF 框架,当 CEF 框架成功创建出 browserView 对象之后,这个 browserView 对象就和 viewDelegate 对象建立了联系。接下来我们看一下 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); };- CefBrowserViewDelegate 类声明了很多与 BrowserView 相关的事件方法,这里我们只实现了它的 OnPopupBrowserViewCreated 方法,当页面请求弹出一个新页面时,这个方法被调用。
- 方法的第一个参数是弹窗来源页面对应的 BrowserView,第二个参数是被弹出的页面对应的 BrowserView ,第三个参数是一个布尔值,代表着被弹出的页面是不是开发者调试工具(开发者调试工具也是一个页面)。
- 接下来我们看一下这个方法的实现代码:
#include "ViewDelegate.h" #include "WindowDelegate.h" // 当前页面弹出新窗口时此方法被执行 bool ViewDelegate::OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browserView, CefRefPtr<CefBrowserView> popupBrowserView, bool isDevtools) { CefWindow::CreateTopLevelWindow(new WindowDelegate(popupBrowserView)); return true; }- 实现逻辑很简单,仅仅是通过 CefWindow 类的静态方法 CreateTopLevelWindow 创建了一个顶级窗口,创建这个窗口的时候我们把自定义的 WindowDelegate 对象附加到目标 BrowserView 上了,这与我们在 App 类中创建第一个窗口的逻辑没什么两样。
- 值得注意的是,这个方法传入的 popupBrowserView 对象,默认也是被 ViewDelegate 控制的,也就是说在一个弹出窗口的页面上,通过点击连接再弹出一个窗口,也会进入 OnPopupBrowserViewCreated 事件。这就使得整个应用中所有的页面都具备了弹出新窗口这项能力。
- 最后一个窗口关闭时退出应用
- 虽然程序可以成功地打开页内弹窗了,而且还可以在新弹窗里点击连接打开更多的弹窗,但你会发现我们的应用打开多个窗口之后,无法通过关闭窗口退出应用了。这是因为我们退出应用的逻辑是放置在窗口代理类的 OnWindowDestroyed 事件中的(我们在这个事件中调用了 CefQuitMessageLoop 方法),当这个事件在某一个窗口触发时,其他窗口和页面尚未释放,所以就爆出了异常信息。
- 为了解决这个问题,我们必须把创建的页面管理起来,明确是最后一个窗口关闭时再退出应用。 要想做到这一点就必须知道什么时候页面创建成功了、什么时候页面被销毁了。为此我们引入了一个新的类型:PageHandler。这个类型负责处理与页面有关的事件,同样它也是在 App 类的 OnContextInitialized方法中创建并与 BrowserView 进行关联的,代码如下所示:
CefRefPtr<PageHandler> pageHandler(new PageHandler()); CefRefPtr<ViewDelegate> viewDelegate(new ViewDelegate()); CefRefPtr<CefBrowserView> browserView = CefBrowserView::CreateBrowserView(pageHandler, url, settings, nullptr, nullptr, viewDelegate);- 上面这段代码中,我们把 PageHandler 的对象作为第一个参数传递给了 CreateBrowserView 方法,CEF 会把 PageHandler 的对象与我们创建的 BrowserView 对象关联起来。当页面创建成功时,PageHandler 类内定义的方法将被 CEF 调用。
- PageHandler 的头文件的代码如下:
#pragma once #include "include/cef_app.h" #include <list> 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; };- 这个类的第一个基类是 CefClient, CefClient 类定义了一系列的页面行为接口,并通过这些接口返回具体某项事务的处理对象,比如打印处理对象、展示处理对象、菜单处理对象等,在本案例中我们用到了它的 GetLifeSpanHandler 接口。这个接口返回一个 CefLifeSpanHandler 类型的实例指针,用于处理页面生命周期中的事件。
- 为了简单,我们直接让 PageHandler 也继承自 CefLifeSpanHandler 类(第二个基类),这样在 GetLifeSpanHandler 方法时只要返回它自身就可以了。当然你也可以创建一个新的继承自 CefLifeSpanHandler 的类,在 GetLifeSpanHandler 方法中返回这个新类的实例指针。
- 在这个类中我们实现了 CefLifeSpanHandler 父类的两个方法:OnAfterCreated 方法和 OnBeforeClose 方法。当页面被成功创建后,CEF 框架会主动调用 OnAfterCreated 方法;当页面被关闭之前,CEF框架会主动调用 OnBeforeClose 方法。这两个方法的入参都是一个 CefBrowser 指针。
- 另外,我们还在这个类中创建了一个名为 browsers 的私有变量,这个变量是一个标准 C++ 的 list 容器,用于存放 CefBrowser 类型的指针。
- 下面我们来看一下 PageHandler 这个类的实现代码:
#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(); } }- 当页面被成功创建后,我们把得到的 browser 对象存入了 browsers 容器。当页面被关闭之前,我们遍历这个容器,并把即将被关闭的页面从容器中删除。最后判断容器是否为空,如果容器已经被清空了,说明所有的页面都关闭了,那么此时发射退出应用的消息。
- 最后不要忘记修改 WindowDelegate 类的 OnWindowDestroyed 方法,把我们之前写的 CefQuitMessageLoop 语句删掉。
- 页面阻止窗口关闭
- 阻止窗口关闭并请求用户确认的 JavaScript 代码如下所示:
window.onbeforeunload = function(){ return "askForClose" }- 然而把上面这段代码加入到我们的 JavaScript 文件中却起不到任何阻止窗口关闭的效果。接下来我们就介绍 CEF 框架是如何让这段代码生效的。
- 注意,弹窗中提示的内容与我们在 onbeforeunload

文章详细介绍了在CEF框架下如何实现窗口控制,包括创建新窗口、阻止窗口关闭、自定义对话框以及处理JavaScript弹窗,如alert、confirm和prompt。此外,还讨论了如何打开开发者调试工具DevTools,并展示了自定义标题栏和拖拽效果的实现。
最低0.47元/天 解锁文章
1672

被折叠的 条评论
为什么被折叠?



