【CEF】《CEF 桌面软件开发实战》笔记-Chapter3-窗口控制

文章详细介绍了在CEF框架下如何实现窗口控制,包括创建新窗口、阻止窗口关闭、自定义对话框以及处理JavaScript弹窗,如alert、confirm和prompt。此外,还讨论了如何打开开发者调试工具DevTools,并展示了自定义标题栏和拖拽效果的实现。
三、窗口控制
  1. 新窗口没有大小
  • 如果在我的第一个页面中,增加一个 target 为 _blank 的链接(只有把 target 属性设置为 _blank 时,用户点击链接才会打开新窗口),如下代码所示:
    <a href="my://bread/index.html?a=123" target="_blank">打开一个新窗口</a>
    
    • 注意,这里我们尝试打开的是我们自定义协议所指向的页面地址,而且这个地址就是当前页面的地址,也就是说打开的新窗口加载的也是当前页面,而且在新窗口也可以通过点击这个连接打开另一个新窗口。
    • 运行程序,点击这个连接,虽然可以打开一个新窗口,但这个窗口却没有尺寸。
  • 实际上,如果我们没有为第一个窗口提供 WindowDelegate 对象的话,我们的第一个窗口也会是这样的,但第一个窗口的 WindowDelegate 对象是我们在 CreateTopLevelWindow 时手动附加上去的,那么类似这种用户自己打开的窗口我们该如何动态把 WindowDelegate 对象附加上去呢?
  • 用户打开新窗口这个事件是发生在 ViewDelegate 对象上的,我们在前面的示例中并没有定义这个对象,所以就没有机会把 WindowDelegate 附加到新窗口上去。接下来我们就定义一下这个对象。
  1. 创建新窗口的事件
  • 这个 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 事件。这就使得整个应用中所有的页面都具备了弹出新窗口这项能力。
  1. 最后一个窗口关闭时退出应用
  • 虽然程序可以成功地打开页内弹窗了,而且还可以在新弹窗里点击连接打开更多的弹窗,但你会发现我们的应用打开多个窗口之后,无法通过关闭窗口退出应用了。这是因为我们退出应用的逻辑是放置在窗口代理类的 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 语句删掉。
  1. 页面阻止窗口关闭
  • 阻止窗口关闭并请求用户确认的 JavaScript 代码如下所示:
    window.onbeforeunload = function(){
         
         
        return "askForClose"
    }
    
    • 然而把上面这段代码加入到我们的 JavaScript 文件中却起不到任何阻止窗口关闭的效果。接下来我们就介绍 CEF 框架是如何让这段代码生效的。
    • 注意,弹窗中提示的内容与我们在 onbeforeunload
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值