1. 程式人生 > >MFC應用程式中嵌入一個谷歌cef瀏覽器

MFC應用程式中嵌入一個谷歌cef瀏覽器

#include "include/base/cef_lock.h"
#include "include/cef_client.h"

class ClientHandler : public CefClient,
                      public CefDisplayHandler,
                      public CefLifeSpanHandler,
                      public CefLoadHandler 
{
public:
   // Implement this interface to receive notification of ClientHandler
   // events. The methods of this class will be called on the main thread.
   class Delegate 
   {
   public:
      // Called when the browser is created.
      virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) = 0;

      // Called when the browser is closing.
      virtual void OnBrowserClosing(CefRefPtr<CefBrowser> browser) = 0;

      // Called when the browser has been closed.
      virtual void OnBrowserClosed(CefRefPtr<CefBrowser> browser) = 0;

      // Set the window URL address.
      virtual void OnSetAddress(std::string const & url) = 0;

      // Set the window title.
      virtual void OnSetTitle(std::string const & title) = 0;

      // Set fullscreen mode.
      virtual void OnSetFullscreen(bool const fullscreen) = 0;

      // Set the loading state.
      virtual void OnSetLoadingState(bool const isLoading,
         bool const canGoBack,
         bool const canGoForward) = 0;

   protected:
      virtual ~Delegate() {}
   };

 public:
  ClientHandler(Delegate* delegate);
  ~ClientHandler();

  void CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url);

  // CefClient methods:
  virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
  virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
  virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

  // CefDisplayHandler methods:
  virtual void OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url) override;
  virtual void OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title) override;
  virtual void OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen) override;

  // CefLifeSpanHandler methods:
  virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
  virtual bool DoClose(CefRefPtr<CefBrowser> browser) override;
  virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;

  // CefLoadHandler methods:
  virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
     bool isLoading,
     bool canGoBack,
     bool canGoForward) override;

  virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
                           CefRefPtr<CefFrame> frame,
                           ErrorCode errorCode,
                           const CefString& errorText,
                           const CefString& failedUrl) override;

  // This object may outlive the Delegate object so it's necessary for the
  // Delegate to detach itself before destruction.
  void DetachDelegate();

private:
  
  // Include the default reference counting implementation.
  IMPLEMENT_REFCOUNTING(ClientHandler);
  // Include the default locking implementation.
  IMPLEMENT_LOCKING(ClientHandler);

private:
   Delegate* m_delegate;
};

  • clienthandler繼承於幾個類:
  • cefclient:用於處理程式實現的介面。
  • cefdisplayhandler:處理瀏覽器的顯示狀態相關事件的介面;這類方法被稱為在UI執行緒。
  • ceflifespanhandler:處理瀏覽器的壽命相關事件的介面;這類方法被稱為UI執行緒上除非另有規定。
  • cefloadhandler:處理瀏覽器的負載狀況相關事件的介面;這類方法被稱為在瀏覽器UI執行緒或程序的主執行緒渲染過程。
由於clienthandler類 對三個方法進行虛擬繼承。
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

內部委託類代表一個介面接收各種事件的通知,如瀏覽器建立和銷燬、修改的網址等。這將由建立瀏覽器的檢視來實現。
建立瀏覽器提供了一種方法呼叫createbrowser()。這實際上只是呼叫適當的引數cefbrowserhost:createbrowser 。
void ClientHandler::CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url)
{
   CefBrowserHost::CreateBrowser(info, this, url, settings, nullptr);
}

處理程式介面方法的實現在委託介面中呼叫方法,給檢視一個訊息,以處理一個特定事件響應。
void ClientHandler::OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url)
{
   CEF_REQUIRE_UI_THREAD();

   // Only update the address for the main (top-level) frame.
   if(frame->IsMain())
   {
      if(m_delegate != nullptr)
         m_delegate->OnSetAddress(url);
   }
}

void ClientHandler::OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetTitle(title);
}

void ClientHandler::OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetFullscreen(fullscreen);
}

void ClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnBrowserCreated(browser);
}

bool ClientHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnBrowserClosing(browser);

   return false;
}

void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnBrowserClosed(browser);
}

void ClientHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
   CefRefPtr<CefFrame> frame,
   ErrorCode errorCode,
   const CefString& errorText,
   const CefString& failedUrl)
{
   CEF_REQUIRE_UI_THREAD();

   // Don't display an error for downloaded files.
   if(errorCode == ERR_ABORTED)
      return;

   // Display a load error message.
   std::stringstream ss;
   ss << "<html><body bgcolor=\"white\">"
      "<h2>Failed to load URL " << std::string(failedUrl) <<
      " with error " << std::string(errorText) << " (" << errorCode <<
      ").</h2></body></html>";
   frame->LoadString(ss.str(), failedUrl);
}

void ClientHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> browser, bool isLoading, bool canGoBack, bool canGoForward)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetLoadingState(isLoading, canGoBack, canGoForward);
}
委託的例項是在detachdelegate()建構函式初始化。
ClientHandler::ClientHandler(Delegate* delegate)
   : m_delegate(delegate)
{
}

void ClientHandler::DetachDelegate()
{
   m_delegate = nullptr;
}

在cefview類
這是CView的實施,而且clienthandler::代表介面。
實現的背後的總體思路是:當檢視建立時也建立一個瀏覽器,並顯示在視窗的頂部;每當檢視視窗的大小的變化也改變了瀏覽器視窗的大小,以覆蓋整個客戶區。
為了使事情複雜化一點,瀏覽器將首先顯示一個啟動頁,從磁碟載入,包含一些一般性的資訊。然後使用者可以通過在應用程式的位址列中鍵入它來導航到任何的網址。
在oninitialupdate()方法我們做以下:
建立將從磁碟載入的啟動頁的網址
建立cefwindowinfo表示瀏覽器將被建立並設定檢視作為瀏覽器的檢視客戶區對於瀏覽器視窗矩形的父視窗資訊的例項。
建立cefbrowsersettings表示瀏覽器初始化設定和設定web_security到state_disabled例項(這意味著禁用Web安全限制,即同源策略)。
建立clienthandler指定檢視例項為代表和建立瀏覽器指定視窗的資訊的一個例項,瀏覽器設定和初始的URL。
void CefView::OnInitialUpdate()
{
   CView::OnInitialUpdate();

   InitStartUrl();

   auto rect = RECT{0};
   GetClientRect(&rect);

   CefWindowInfo info;
   info.SetAsChild(GetSafeHwnd(), rect);

   CefBrowserSettings browserSettings;
   browserSettings.web_security = STATE_DISABLED;

   m_clientHandler = new ClientHandler(this);
   m_clientHandler->CreateBrowser(info, browserSettings, CefString(m_startUrl));
}

void CefView::InitStartUrl()
{
   TCHAR path_buffer[_MAX_PATH] = {0};
   TCHAR drive[_MAX_DRIVE] = {0};
   TCHAR dir[_MAX_DIR] = {0};
   TCHAR fname[_MAX_FNAME] = {0};
   TCHAR ext[_MAX_EXT] = {0};

   ::GetModuleFileName(NULL, path_buffer, sizeof(path_buffer));
   auto err = _tsplitpath_s(path_buffer, drive, _MAX_DRIVE, dir, _MAX_DIR, fname, _MAX_FNAME, ext, _MAX_EXT);
   if(err != 0) {}

   auto s = CString{dir};
   s += _T("html");
   err = _tmakepath_s(path_buffer, _MAX_PATH, drive, (LPCTSTR)s, _T("index"), _T("html"));
   if(err != 0) {}

   m_startUrl = CString {path_buffer};
   m_startUrl.Replace(_T('\\'),_T('/'));
   m_startUrl = CString {_T("file:///")} + m_startUrl;
}

該ClientHandler::Delegate介面相對比較簡單,不需要太多的解釋。值得注意的是,在onsetaddress我們是否有新的URL匹配的啟動URL,在這種情況下我們設定在應用程式的位址列沒有文字。
void CefView::OnBrowserCreated(CefRefPtr<CefBrowser> browser)
{
   m_browser = browser;
}

void CefView::OnBrowserClosing(CefRefPtr<CefBrowser> browser)
{
}

void CefView::OnBrowserClosed(CefRefPtr<CefBrowser> browser)
{
   if(m_browser != nullptr && 
      m_browser->GetIdentifier() == browser->GetIdentifier())
   {
      m_browser = nullptr;

      m_clientHandler->DetachDelegate();
   }
}

void CefView::OnSetAddress(std::string const & url)
{
   auto main = static_cast<CMainFrame*>(m_wndMain);
   if(main != nullptr)
   {
      auto newurl = CString {url.c_str()};
      if(newurl.Find(m_startUrl) >= 0)
         newurl = "";

      main->SetUrl(newurl);
   }
}

void CefView::OnSetTitle(std::string const & title)
{
   ::SetWindowText(m_hWnd, CefString(title).ToWString().c_str());
}

void CefView::OnSetFullscreen(bool const fullscreen)
{
   if(m_browser != nullptr)
   {
      if(fullscreen)
      {
         CefWindowsHelpers::Maximize(m_browser);
      }
      else 
      {
         CefWindowsHelpers::Restore(m_browser);
      }
   }
}

void CefView::OnSetLoadingState(bool const isLoading,
   bool const canGoBack,
   bool const canGoForward)
{
}
我們要做的一件事是在檢視瀏覽器視窗大小每次檢視更改大小。由於瀏覽器視窗是完全重疊的檢視的客戶區,它們的大小必須是相同的所有時間。所以在認為我們必須處理的wm_size訊息和調整瀏覽器。
void CefView::OnSize(UINT nType, int cx, int cy)
{
   CView::OnSize(nType, cx, cy);

   if(m_clientHandler != nullptr)
   {
      if(m_browser != nullptr)
      {
         auto hwnd = m_browser->GetHost()->GetWindowHandle();
         auto rect = RECT {0};
         GetClientRect(&rect);

         ::SetWindowPos(hwnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
      }
   }   
}

另一個特點,應用程式支援是重新整理當前頁面時按F5。由於瀏覽器的重疊檢視客戶區,處理wm_keydown訊息做事時,按下F5我們積極看訊息受父母才被進一步。這是壓倒一切的pretranslatemessage()檢視中完成。
BOOL CefView::PreTranslateMessage(MSG* pMsg)
{
   if(pMsg->message == WM_KEYDOWN)
   {
      if(pMsg->wParam == VK_F5)
      {
         m_browser->Reload();
      }
   }

   return CView::PreTranslateMessage(pMsg);  
}

建立應用
唯一重要的事情是去初始化應用程式的初始化和反初始化。這是initinstance()和exitinstance()做的cefmfcddemoapp(派生類CWinApp)。在initinstance()我們稱initializecef()和exitinstance()我們稱uninitializecef()。
這些做什麼:
初始化函式呼叫cefinitialize初始化CEF瀏覽器程序,通過幾個引數:應用引數,應用程式設定,和cefapp物件。在應用程式設定,我們設定multi_threaded_message_loop假這意味著我們必須從我們的應用程式訊息看cefdomessageloopwork()。
uninitialize函式的簡單呼叫cefshutdown()關閉CEF瀏覽器程序程式退出之前。
void CefMfcdDemoApp::InitializeCef()
{
   CefMainArgs mainargs(m_hInstance);

   CefSettings settings;
   settings.multi_threaded_message_loop = false;
   
   CefInitialize(mainargs, settings, m_app, nullptr);
}

void CefMfcdDemoApp::UninitializeCef()
{
   CefShutdown();
}

BOOL CefMfcdDemoApp::InitInstance()
{
   // various initialization

   InitializeCef();

   CWinApp::InitInstance();

   // more initialization

   return TRUE;
}

int CefMfcdDemoApp::ExitInstance()
{
	AfxOleTerm(FALSE);

  UninitializeCef();

	return CWinApp::ExitInstance();
}
因為我們指定的瀏覽器程序不應該執行在一個單獨的執行緒的訊息迴圈(通過設定multi_threaded_message_loop 為假)我們需要從主執行緒的訊息迴圈呼叫cefdomessageloopwork()。我們通過重寫CWinApp:::pumpmessage()如下。
BOOL CefMfcdDemoApp::PumpMessage()
{
   auto result = CWinApp::PumpMessage();

   CefDoMessageLoopWork();
   
   return result;
}

最後把它全部組合起來OK
程式碼連結