在客户端程序中嵌入浏览器,有两种方式,一种是使用微软的IE控件,一种是使用CEF。这里介绍的是使用CWebBrowser2类(在MFC程序中插入IE的Active控件生成),定制内嵌浏览器窗口的一些经验。
本文的经验积累于实现逆战退出游戏时候的广告弹窗的过程中,下面Show一下这个自带萌妹子的弹窗吧。
这是一个无边框的Windows对话框程序,并且是一个基于背景图片的不规则弹窗窗口;内部嵌入了一个浏览器控件窗口,这个漂亮的妹子就是浏览器控件打开的网页显示出来的。对这个妹子有兴趣的,可以去玩一把逆战,退出客户端的时候就会出来这个弹窗了。
下面介绍一些关于实现该弹窗浏览器的Tips。
一、如何获得CWebBrowser2 方法1:网络搜索下载,比如我以前的一篇博文里面有下载链接:vc内嵌浏览器。
方法2:在MFC程序中插入IE对应的Activex控件,工程中就会生成这个类。
为了定制浏览器窗口,我继承了该类,自定义了浏览器窗口类CYXBrwser。
二、让浏览器窗口适应对话框窗口大小 在对话框类的OnInitDialog()函数中,添加如下代码:
1 2 3 4 5 6 7 8 m_pBrowser = new CYXBrowser(); RECT rect; GetClientRect(rect); rect.left += 8; rect.right -= 8; rect.top += 8; rect.bottom -= 1; m_pBrowser->Create(TEXT("NZBrowser"), WS_CHILD | WS_VISIBLE, rect, this, MY_IEBROWSER_ID);
注意,rect的大小需要调节来获得需要的效果。
三、屏蔽右键 有种比较的方法是在PreTranslateMessage中过滤WM_RBUTTONDOWN消息。
1 2 3 4 5 6 7 8 9 10 11 BOOL CYXBrowser::PreTranslateMessage(MSG* pMsg) { if (WM_RBUTTONDOWN == pMsg->message) { return TRUE; } return CWnd::PreTranslateMessage(pMsg); }
四、隐藏网页的滚动条 这是最难处理的一个地方。不仅仅需要修改程序,而且需要web端的配合。
第一步:添加DocumentComplete事件响应。在C***Dlg的cpp中添加如下宏:
1 2 3 BEGIN_EVENTSINK_MAP (CClientBrowserDlg, CDialog) ON_EVENT(CClientBrowserDlg, MY_IEBROWSER_ID, DISPID_DOCUMENTCOMPLETE, DocumentComplete, VTS_DISPATCH VTS_PVARIANT) END_EVENTSINK_MAP ()
注意,CClientBrowserDlg是响应函数所在的类,MY_IEBROWSER_ID是二中指定的浏览器窗口ID。DocumentComplete是CClientBrowserDlg中的响应该事件的成员函数。
第二步:实现该函数,直接贴代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 void CClientBrowserDlg::DocumentComplete(LPDISPATCH pDisp, VARIANT* URL) { UNUSED_ALWAYS(pDisp); ASSERT(V_VT(URL) == VT_BSTR); CString str(V_BSTR(URL)); m_pBrowser->OnDocumentComplete(str); } void CYXBrowser::OnDocumentComplete(LPCTSTR lpszURL) { m_bDocumentComplete = true; HideScrollBar(); } void CYXBrowser::HideScrollBar() { HRESULT hr; IDispatch *pDisp = GetDocument(); IHTMLDocument2 *pDocument = NULL; IHTMLElement* pEl; IHTMLBodyElement *pBodyEl; if (pDisp) { hr = pDisp->QueryInterface(IID_IHTMLDocument2, (void**)pDocument); if (!SUCCEEDED(hr)) { return; } } if (pDocument SUCCEEDED(pDocument->get_body(pEl))) { if (pEl SUCCEEDED(pEl->QueryInterface(IID_IHTMLBodyElement, (void**)pBodyEl))) { pBodyEl->put_scroll(L"no");//去滚动条 } IHTMLStyle *phtmlStyle; pEl->get_style(phtmlStyle); if (phtmlStyle != NULL) { phtmlStyle->put_overflow(L"hidden"); phtmlStyle->put_border(L"none");// 去除边框 phtmlStyle->Release(); pEl->Release(); } } }
关键函数是HideScrollBar()。
第三步:在浏览器内嵌网页的最前面添加,
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
注意,第三步是不可缺少的。
五、屏蔽多次点击浏览器窗口的提示:”服务器正在运行中”要选择”切换到…”或”重试”的对话框** 在CClientBrowserDlg::OnInitDialog()中添加如下代码,
1 2 3 4 5 AfxOleGetMessageFilter()->EnableBusyDialog(FALSE); AfxOleGetMessageFilter()->SetBusyReply(SERVERCALL_RETRYLATER); AfxOleGetMessageFilter()->EnableNotRespondingDialog(TRUE); AfxOleGetMessageFilter()->SetMessagePendingDelay(-1);
六、点击网页打开系统默认浏览器 第一步:绑定NEWWINDOW2事件。
1 ON_EVENT (CClientBrowserDlg, MY_IEBROWSER_ID, DISPID_NEWWINDOW2, OnNewWindow2, VTS_PDISPATCH VTS_PBOOL)
第二步:设置该OnNewWindow2的*bCancel为true,并且调用ShellExecute打开网页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void CClientBrowserDlg::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* bCancel) { m_pBrowser->OnNewWindow2(ppDisp, bCancel); } void CYXBrowser::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* bCancel) { *bCancel = TRUE;//禁止弹出新窗口(因为会使用IE弹窗) HRESULT hr; IDispatch *pDisp = GetDocument(); IHTMLDocument2 *pHTMLDocument2 = NULL; if (pDisp) { hr = pDisp->QueryInterface(IID_IHTMLDocument2, (void**)pHTMLDocument2); if (!SUCCEEDED(hr)) { return; } } if (pHTMLDocument2 != NULL) { CComPtr<IHTMLElement> pIHTMLElement; pHTMLDocument2->get_activeElement(pIHTMLElement); if (pIHTMLElement != NULL) { variant_t url; hr = pIHTMLElement->getAttribute(L"href", 0, url); if (SUCCEEDED(hr)) { CString strURL(V_BSTR(url)); ShellExecute(m_hWndOwner, NULL, strURL, NULL, NULL, SW_NORMAL); } } } }
处理了六,五也就不需要了,因为点击网页不会再弹出IE浏览器了。
七、为网页元素的添加事件处理:比如web按钮的点击等 第一步:继承CCmdTarget新建类CHtmlEventHandle,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #pragma once#import <mshtml.tlb>class CYXBrowser; class CHtmlEventHandle : public CCmdTarget { DECLARE_DYNAMIC(CHtmlEventHandle) public: CHtmlEventHandle(); virtual ~CHtmlEventHandle(); public: void SetWnd(CWnd* pWnd) { m_pWnd = pWnd;} void SetWebBrowser(CYXBrowser* pWebBroswer) { m_pWebBrowser = pWebBroswer; } void OnClick(MSHTML::IHTMLEventObjPtr pEvtObj); private: CWnd* m_pWnd; CYXBrowser* m_pWebBrowser; protected: DECLARE_MESSAGE_MAP() DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() }; #include "stdafx.h" #include "ClientBrowser.h" #include "HtmlEventHandle.h" #include "mshtmdid.h" #include "MsHTML.h" #include "YXBrowser.h" IMPLEMENT_DYNAMIC (CHtmlEventHandle, CCmdTarget) CHtmlEventHandle::CHtmlEventHandle() { EnableAutomation(); // 重要:激活 IDispatch } CHtmlEventHandle::~CHtmlEventHandle() { } BEGIN_MESSAGE_MAP (CHtmlEventHandle, CCmdTarget) END_MESSAGE_MAP () BEGIN_DISPATCH_MAP (CHtmlEventHandle, CCmdTarget) DISP_FUNCTION_ID(CHtmlEventHandle, "HTMLELEMENTEVENTS2_ONCLICK" , DISPID_HTMLELEMENTEVENTS2_ONCLICK, OnClick, VT_EMPTY, VTS_DISPATCH) END_DISPATCH_MAP () BEGIN_INTERFACE_MAP (CHtmlEventHandle, CCmdTarget) INTERFACE_PART(CHtmlEventHandle, DIID_HTMLButtonElementEvents2, Dispatch) END_INTERFACE_MAP () void CHtmlEventHandle::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj) { MSHTML::IHTMLElementPtr pElement = pEvtObj->GetsrcElement(); // 事件发生的对象元素 while(pElement) { _bstr_t strId; pElement->get_id(strId.GetBSTR()); if (_bstr_t(HTML_CLOSE_BUTTON) == strId) { PostQuitMessage(0); break; } else if (_bstr_t(HTML_SET_BUTTON) == strId) { } pElement = pElement->GetparentElement(); } }
第二步:在CYXBrowser中注册这个web事件处理类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 private: void InstallEventHandler(); void UninstallEventHandler(); private: CHtmlEventHandle *m_pEventHandler; DWORD m_dwDocCookie; // 用于卸载事件响应函数 IDispatch *m_pDispDoc; // 用于卸载事件响应函数 bool m_bDocumentComplete; void CYXBrowser::InstallEventHandler() { if (m_dwDocCookie) UninstallEventHandler(); m_pDispDoc = GetDocument(); IConnectionPointContainerPtr pCPC = m_pDispDoc; IConnectionPointPtr pCP; pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, pCP); IUnknown* pUnk = m_pEventHandler->GetInterface(IID_IUnknown); HRESULT hr = pCP->Advise(pUnk, m_dwDocCookie); if (!SUCCEEDED(hr)) m_dwDocCookie = 0; } void CYXBrowser::UninstallEventHandler() { if(0 == m_dwDocCookie) return; IConnectionPointContainerPtr pCPC = m_pDispDoc; IConnectionPointPtr pCP; pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, pCP); HRESULT hr = pCP->Unadvise(m_dwDocCookie); } CYXBrowser::CYXBrowser() { m_pEventHandler = new CHtmlEventHandle; m_pEventHandler->SetWnd(m_pParent); m_pEventHandler->SetWebBrowser(this); m_dwDocCookie = 0; // 用于卸载事件响应函数 m_pDispDoc = NULL; // 用于卸载事件响应函数 m_bDocumentComplete = false; } void CYXBrowser::OnDocumentComplete(LPCTSTR lpszURL) { m_bDocumentComplete = true; HideScrollBar(); InstallEventHandler(); } void CYXBrowser::OnBeforeNavigate2(LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel) { UninstallEventHandler(); } void CYXBrowser::OnDestroy() { UninstallEventHandler(); CWebBrowser2::OnDestroy(); }
现在就可以在void CHtmlEventHandle::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)函数内捕获网页按钮之类的点击了。处理代码的思路是从当前元素开始,不断往上查找父元素,直到匹配的元素ID为止。
八、判断url是否有效,如果无效则打开资源url,防止Web页面为空 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 bool CYXBrowser::IsUrlAvailable(CString strUrl) { CInternetSession* session = new CInternetSession(); CInternetFile* file = NULL; bool bAvailable = true; try { file = (CInternetFile*)session->OpenURL(strUrl); } catch (CInternetException*) { bAvailable = false; } delete session; if (!file) { bAvailable = false; } return bAvailable; } if (m_pBrowser->IsUrlAvailable(theApp.m_strUrl)){ m_pBrowser->Navigate2(theApp.m_strUrl); } else { TCHAR szModule[MAX_PATH]; GetModuleFileName(theApp.m_hInstance, szModule, MAX_PATH); theApp.m_strUrl.Format(_T("res://%s/%s"), szModule, theApp.m_strLocalUrl); m_pBrowser->Navigate2(theApp.m_strUrl); }
注意资源url的格式是res://模块名/网页名,因此需要在该程序中导入自定义的资源,并且将其命名为theApp.m_strLocalUrl代表的字符串值,比如”NZ.HTML”。