duilib中,专门设置了浏览器控件
一 CEF浏览器创建
XML加载过程中,会创建自定义的浏览器控件
ui::Control* CefNativeForm::CreateControl(const std::wstring& pstrClass)
{
if (pstrClass == L"CefNativeControl")
{
return new CefNativeControl;
}
if (pstrClass == L"CefControl")
{
return new CefControl;
}
return NULL;
}
在此过程中,会对此控件基本信息赋值,Window::InitControls 比如控件所属窗口等
这个控件,首先是duilib控件,所以继承自control
同时,这个控件要处理一些浏览器事件,所以继承自 浏览器消息委托类 HandlerDelegate
class CefNativeControl :public Control, public nim_cef::BrowserHandler::HandlerDelegate
{
public:
CefNativeControl(void);
~CefNativeControl(void);
virtual void Init() override;
virtual void SetPos(UiRect rc) override;
virtual void HandleMessage(EventArgs& event) override;
virtual void SetVisible(bool bVisible = true) override;
virtual void SetInternVisible(bool bVisible = true) override;
HWND GetCefHandle() const;
public:
// 控件对外的控制接口
void LoadURL(const CefString& url);
void LoadString(const CefString& stringW, const CefString& url);
void GoBack();
void GoForward();
bool CanGoBack();
bool CanGoForward();
void Refresh();
void StopLoad();
bool IsLoading();
void ExecJavaScript(const CefString& js);
同时,有一个重要的变量 浏览器对象 CefRefPtr
// BrowserHandler implements CefClient and a number of other interfaces.
class BrowserHandler :
public nbase::SupportWeakCallback,
public CefClient,
public CefLifeSpanHandler,
public CefRenderHandler,
public CefContextMenuHandler,
public CefDisplayHandler,
public CefDragHandler,
public CefGeolocationHandler,
public CefJSDialogHandler,
public CefKeyboardHandler,
public CefLoadHandler,
public CefRequestHandler,
public CefDownloadHandler
初始化:
void CefNativeControl::Init()
{
if (browser_handler_.get() == nullptr)
{
LONG style = GetWindowLong(m_pWindow->GetHWND(), GWL_STYLE);
SetWindowLong(m_pWindow->GetHWND(), GWL_STYLE, style | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
ASSERT((::GetWindowLong(m_pWindow->GetHWND(), GWL_EXSTYLE) & WS_EX_LAYERED) == 0 && L"无法在分层窗口内使用本控件");
browser_handler_ = new nim_cef::BrowserHandler;
browser_handler_->SetHostWindow(m_pWindow->GetHWND()); // 设置Cef浏览器对象所属的窗体的句柄
browser_handler_->SetHandlerDelegate(this); // 设置委托类指针,浏览器对象的一些事件会交给此指针对象来处理
// 使用有窗模式
CefWindowInfo window_info;
window_info.SetAsChild(this->m_pWindow->GetHWND(), m_rcItem); // 设置浏览器在窗口中的区域 作为子窗口
CefBrowserSettings browser_settings;
CefBrowserHost::CreateBrowser(window_info, browser_handler_, L"", browser_settings, NULL); //创建浏览器
}
__super::Init();
}
OnAfterCreated() 在浏览器对象被创建后立即调用,主应用程序通常用这个函数保存浏览器对象的引用
void BrowserHandler::OnAfterCreated(CefRefPtr browser)
{
REQUIRE_UI_THREAD();
nbase::ThreadManager::PostTask(kThreadUI, ToWeakCallback([this, browser](){
if (browser_client_ == nullptr)
browser_client_ = browser->GetHost()->GetClient();
browser_list_.emplace_back(browser);
if (browser_ != nullptr)
browser_->GetHost()->WasHidden(true);
browser_ = browser;
CefManager::GetInstance()->AddBrowserCount();//浏览器计数,方便销毁时判断是否销毁完毕
// 有窗模式下,浏览器创建完毕后,让上层更新一下自己的位置;因为在异步状态下,上层更新位置时可能Cef窗口还没有创建出来
if (!CefManager::GetInstance()->IsEnableOffsetRender())
{
handle_delegate_->UpdateWindowPos();
}
task_list_after_created_();
task_list_after_created_.Clear();
}));
}
二 CEF浏览器销毁
CefBrowserHost::CloseBrowser(false), 通知HOST开始关闭流程,参数force_close=false,我们是否强制关闭 (true 强制关闭,false交互式关闭)
CefBrowserHost::CloseBrowser(false)或者CefBrowserHost::TryCloseBrowser(),随后触发DoClose(注意:DoClose调用之后OnBeforeClose并不是一定被调用,这个和CefClient的生命周期有关)
void BrowserHandler::OnBeforeClose(CefRefPtr browser)
{
REQUIRE_UI_THREAD();
nbase::ThreadManager::PostTask(kThreadUI, ToWeakCallback([this, browser](){
CefManager::GetInstance()->SubBrowserCount();//浏览器计数
auto it = std::find_if(browser_list_.begin(), browser_list_.end(), [&](const CefRefPtr& item){
return item->IsSame(browser);
});
if (it != browser_list_.end())
{
auto closed_browser = *it;
browser_list_.erase(it);
if (closed_browser->IsSame(browser_))
{
browser_ = browser_list_.size() > 0 ? *browser_list_.rbegin() : nullptr;
if (browser_ != nullptr)
{
browser_->GetHost()->WasHidden(false);
// browser_->Reload();
}
}
}
}));
}
三 duilib cef窗口销毁
含浏览器控件的窗口,销毁时,会将浏览器控件销毁
CefNativeControl::~CefNativeControl(void)
{
ReleaseBroser();
}
void CefNativeControl::ReleaseBroser()
{
if (browser_handler_.get() && browser_handler_->GetBrowser().get())
{
// Request that the main browser close.
browser_handler_->GetBrowserHost()->CloseBrowser(true);
browser_handler_->SetHostWindow(NULL);
browser_handler_->SetHandlerDelegate(NULL);
}
}
浏览器控件销毁 会调用CloseBrowser
从而引起OnBeforeClose,在此改变浏览器计数
结束进程时,不要直接调用::PostQuitMessage,因为这时可能还有浏览器对象没有消息
应该等所有浏览器对象都销毁后再调用::PostQuitMessage
// 当我们需要结束进程时,千万不要直接调用::PostQuitMessage,这是可能还有浏览器对象没有消息
// 应该等所有浏览器对象都销毁后再调用::PostQuitMessage
void CefManager::PostQuitMessage(int nExitCode)
{
#if !defined(SUPPORT_CEF)
::PostQuitMessage(nExitCode);
return;
#endif
if (browser_count_ == 0)
{
Post2UI([nExitCode]()
{
::PostQuitMessage(nExitCode);
});
}
else
{
auto cb = [nExitCode]()
{
CefManager::GetInstance()->PostQuitMessage(nExitCode);
};
nbase::ThreadManager::PostDelayedTask(kThreadUI, cb, nbase::TimeDelta::FromMilliseconds(500));
}
}
实例:
DestroyAllCefWindows()
{
if (m_pCefPopup)
{
DestroyWindow(m_pCefPopup->GetHWND());//::SendMessage(m_pCefPopup->GetHWND(), WM_CLOSE, 0, 0); //发送关闭窗口的消息WM_CLOSE
m_pCefPopup = NULL;
}
if (m_pCefForm)
{
::SendMessage(m_pCefForm->GetHWND(), WM_CLOSE, 0, 0);// DestroyWindow(m_pCefForm->GetHWND());// ::SendMessage(m_pCefForm->GetHWND(), WM_CLOSE, 0, 0); //发送关闭窗口的消息WM_CLOSE
m_pCefForm = NULL;
}
if (m_pCefUpdate)
{
DestroyWindow(m_pCefUpdate->GetHWND());//::SendMessage(m_pCefUpdate->GetHWND(), WM_CLOSE, 0, 0); //发送关闭窗口的消息WM_CLOSE
m_pCefUpdate = NULL;
}
HelpForm *pForm = (HelpForm*)nim_comp::WindowsManager::SingletonShow(HelpForm::kClassName, 1);
if (pForm)
DestroyWindow(pForm->GetHWND());
nim_cef::CefManager::GetInstance()->PostQuitMessage(0L);
}