CEF多标签浏览器
原创
©著作权归作者所有:来自51CTO博客作者wx637304bacd051的原创作品,请联系作者获取转载授权,否则将追究法律责任
由于工程仅仅提供学习用,怎么简单怎么做。标签关闭通过菜单关闭。通过前面几篇文章学习我们已经可以粗略的实现一个简单的浏览器。如果需要实现多标签浏览器我们需要解决如下几个问题:(Note:Win32的UI线程和Cef的UI线程不是同一个线程,所以在操作的时候需要注意多线程的问题)
- 如何让新打开的网页在标签页下显示?
- 如何在切换标签页的时候切换网页?
- 如何通过关闭标签页的时候关闭网页?
- 如何通过关键字搜索?
- 如何显示devTool?
- 如何新建标签页?
1.如何让新打开的网页在标签页下显示?
通过网页点击打开的新网页是也许POPUP模式,但是我们希望作为标签页窗口。所以需要将窗口属相修改为WS_CHILD并且把父窗口设置为tab窗口。源码如下:
void CBrowserHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
HWND hWnd = browser->GetHost()->GetWindowHandle();
DWORD dwNewStyle = (::GetWindowLong(hWnd, GWL_STYLE)&~(WS_POPUP | WS_CAPTION | WS_BORDER | WS_SIZEBOX | WS_SYSMENU)) | WS_CHILD;
::SetWindowLong(hWnd, GWL_STYLE, dwNewStyle);
SetParent(hWnd, m_parent_hwnd);
// Add to the list of existing browsers.
m_browser_list.push_back(browser);
::PostMessage(g_hMainWnd, UM_NEWPAGE, (WPARAM)browser->GetHost()->GetWindowHandle(), m_browser_list.size() - 1);
}
2.如何在切换标签页的时候切换网页?
通过上面修改属性作为子窗口,我们知道每个CefBrowser都有一个窗口句柄,所以我们可以通过切换标签的时候隐藏没有选中的窗口,显示已经选中的窗口。源码如下:
void CBrowserHandler::SetVisible(bool bVisible, HWND hWnd)
{
if (hWnd == NULL) return;
//Cef的UI线程和对话框的UI线程不在同一个
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
// bind ref cef_closure_task
CefPostTask(TID_UI, base::Bind(&CBrowserHandler::SetVisible, this, bVisible, hWnd));
return;
}
for (int i = 0; i < m_browser_list.size();++i)
{
bool bVisible = (m_browser_list[i]->GetHost()->GetWindowHandle() == hWnd);
::ShowWindow(m_browser_list[i]->GetHost()->GetWindowHandle(), bVisible ? SW_SHOWNORMAL : SW_HIDE);
}
}
3.如何通过关闭标签页的时候关闭网页?
关闭标签页的时候,如果按照之前文章介绍的时候发现关闭某一个网页的时候,这个浏览器进程都关闭了,这不是我们期望的。那么怎么解决呢?通过不断重试发现如果作为POPUP窗口的时候是可以选择性关闭某个网页的。结合TryCloseBrowser关闭机制:关闭的时候会投递WM_CLOSE消息。那么如果我们试着在关闭的时候篡改父窗口,那么是否可以成功呢?结果OK。源码如下:
bool CBrowserApp::TryClose(HWND hWnd)
{
if (hWnd != NULL)
{
//必须更改窗口父窗口属性,否则会退出浏览器进程
::SetParent(hWnd, GetDesktopWindow());
if (m_simple_handler)
m_simple_handler->CloseBrowsers(hWnd);
return true;
}
else
{
if (m_simple_handler && m_simple_handler.get() &&
!m_simple_handler->IsClosing())
{
m_simple_handler->CloseAllBrowsers();
return true;
}
}
return false;
}
void CBrowserHandler::CloseBrowsers(HWND hWnd)
{
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
// bind ref cef_closure_task
CefPostTask(TID_UI, base::Bind(&CBrowserHandler::CloseBrowsers, this, hWnd));
return;
}
if (m_browser_list.empty())
return;
for (int i = 0; i < m_browser_list.size(); ++i)
{
if (m_browser_list[i] && m_browser_list[i]->GetHost()->GetWindowHandle() == hWnd)
{
m_browser_list[i]->GetHost()->TryCloseBrowser();
break;
}
}
}
4.如何通过关键字搜索?
这个我们以百度搜索为例讲解,我们在浏览器输入百度云盘:
所以搜索就很简单了。如果url是以https://或者http://开头我们则直接跳转到该url,否则我们将url转换为utf-8,然后附加在https://www.baidu.com/s?ie=UTF-8&wd=xxxx。源码如下:
LRESULT CCefDemoDlg::OnGoSearch(WPARAM wParam, LPARAM lParam)
{
HWND hNowWnd = GetCurselWnd();
if (hNowWnd && m_browser_app)
{
CString strurl;
m_url.GetWindowTextW(strurl);
//如果是以http或者https开始则直接跳转,否则以百度搜索引擎搜索
//以百度为搜索引擎
//https://www.baidu.com/s?ie=UTF-8&wd=%E7%99%BE%E5%BA%A6
std::wstring wurl = strurl.GetBuffer(0);
if (wurl.substr(0,8) == L"https://" || wurl.substr(0,7) == L"http://")
m_browser_app->LoadUrl(hNowWnd, strurl.GetBuffer(0));
else
{
std::string str = "https://www.baidu.com/s?ie=UTF-8&wd=";
std::string utf8_keywors = "";
UnicodeToUtf8(strurl.GetBuffer(0), utf8_keywors);
str += utf8_keywors;
m_browser_app->LoadUrl(hNowWnd, str);
}
}
return TRUE;
}
void CBrowserHandler::LoadUrl(HWND hWnd, CefString url)
{
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
// bind ref cef_closure_task
CefPostTask(TID_UI, base::Bind(&CBrowserHandler::LoadUrl, this, hWnd, url));
return;
}
if (m_browser_list.empty())
return;
for (int i = 0; i < m_browser_list.size(); ++i)
{
if (m_browser_list[i] && m_browser_list[i]->GetHost()->GetWindowHandle() == hWnd)
{
m_browser_list[i]->GetMainFrame()->LoadURL(url);
break;
}
}
}
5.如何显示devTool?
Cef本身支持直接显示devTool,所以我们只需要选定需要显示的CefBrowser就行(devTool本身也是CefBrowser)。源码如下:
void CBrowserHandler::ShowDevTool(HWND hWnd)
{
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
// bind ref cef_closure_task
CefPostTask(TID_UI, base::Bind(&CBrowserHandler::ShowDevTool, this, hWnd));
return;
}
if (m_browser_list.empty())
return;
for (int i = 0; i < m_browser_list.size(); ++i)
{
if (m_browser_list[i] && m_browser_list[i]->GetHost()->GetWindowHandle() == hWnd)
{
CefWindowInfo windowInfo;
CefRefPtr<CefClient> client;
CefBrowserSettings settings;
if (!m_browser_list[i]->GetHost()->HasDevTools())
m_browser_list[i]->GetHost()->ShowDevTools(windowInfo, this, settings, CefPoint());
break;
}
}
}
6.如何新建标签页?
首先新建标签页分为两类:1.指定主页,2.空白页
我们先说空白页,新建空白页的时候并没有创建CefBrowser,我们只是单纯的建立了一个标签。然后在地址栏输入地址,进行搜索的时候把以该地址新建一个CefBrowser绑定到该标签页下即可。指定主页就类似于空白页的下半部分。所以我们只讲解指定主页的,因为关联到指定标签属于UI层的工作,该工作略,源码如下:
void CBrowserApp::NewTabPage(std::string url)
{
RECT rtClient;
::GetClientRect(m_parent_hwnd, &rtClient);
rtClient.top += 40;
CefWindowInfo wnd_info;
wnd_info.SetAsChild(m_parent_hwnd, rtClient);
CefBrowserSettings browser_settings;
CefBrowserHost::CreateBrowser(wnd_info, m_simple_handler, url, browser_settings, NULL);
}
如果上述功能实现了,我们结合之前的文章说明,那么一个多标签浏览器就成功实现了。
Note:在实践过程中我们发现在Edit里面Enter的时候Cef会触发断言,所以我们需要在PreTranslateMessage拦截Enter消息。
BOOL CCefDemoDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
if (pMsg->message == WM_KEYDOWN)
{
if (pMsg->wParam == VK_RETURN)
{
if (&m_url == GetFocus())
{
SetFocus();
PostMessage(UM_GOSEARCH, 0, 0);
return TRUE;
}
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
整体效果展示: