浏览器的定制与扩展
下載源代碼 
本文分如下章節: 
- 前言
- 在MFC中使用瀏覽器
- 怎樣擴展或定制瀏覽器
- 定制鼠標右鍵彈出出菜單
- 實現腳本擴展(很重要的external接口)
- C++代碼中如何調用網頁腳本中的函數
- 定制消息框的標題
- 怎樣定制、修改瀏覽器向Web服務器發送的HTTP請求頭
- 怎樣修改瀏覽器標識
- 去掉討厭的異常警告
- 怎樣處理瀏覽器內的拖放
- 怎樣禁止網頁元素的選取
- 其它
前言
由于本人在開發中經常要在程序中嵌入瀏覽器,為了符合自己的需求經常要對瀏覽器進行擴展和定制, 解決這些問題需在網上找資料和學習的過程,我想可能很多開發者或許會遇到同樣的問題,特寫此文,以供大家參考。
在MFC中使用瀏覽器
在MFC中微軟為我們提供了CHtmlView、CDHtmlDialog類讓我們的程序很方便的嵌入瀏覽器和進行瀏覽器的二次開發,這比直 接使用WebBrowser控件要方便很多,所以本文中討論的瀏覽器的問題都是針對CHtmlView來討論的。文中將提到一個類CLhpHtmlView, 它是CHtmlView的派生類,文中提及的擴展或定制都將在CLhpHtmlView類(或派生類)上實現。
怎樣擴展或定制瀏覽器
瀏覽器定義了一些擴展接口(如IDocHostUIHandler可以定制瀏覽器界面有關的行為),以便開發者進行定制和擴展。瀏覽 器會在需要的時候向他的控制站點查詢這些接口,在控制站點里實現相應的接口就可以進行相應的擴展。在MFC7.01類 庫中,CHtmlView使用的控制站點是CHtmlControlSite的,在CHtmlControlSite類中 只實現了接口IDocHostUIHandler, 而要實現更多的擴展接口,必須用自定義的控制站類來取代CHtmlControlSite,在下文中提及的類CDocHostSite即為自定義 的控制站類。
關于接口的介紹請參考:http://dev.csdn.net/develop/article/48/48483.shtm
如何使自定義的控制站點來替換默認的控制站點呢?在MFC7.0中只需重載CHtmlView的虛函數CreateControlSite即可: 
定制鼠標右鍵彈出出菜單
要定制瀏覽器的鼠標右鍵彈出菜單,必須在自定義的控制站點類中實現IDocHostUIHandler2接口,并且IE的 版本是5.5或以上。在接口IDocHostUIHandler2的ShowContextMenu方法中調用瀏覽器類的OnShowContextMenu虛函數,我們 在瀏覽器類的派生類重載此虛函數即可實現右鍵菜單的定制,參見代碼
HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID,POINT * ppt,IUnknown * pcmdtReserved,IDispatch * pdispReserved) {METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);return pThis->m_pView->OnShowContextMenu( dwID, ppt, pcmdtReserved,pdispReserved ); }HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt,LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved) {HRESULT result = S_FALSE;switch(m_ContextMenuMode){case NoContextMenu: // 無菜單result=S_OK;break;case DefaultMenu: // 默認菜單break;case TextSelectionOnly: // 僅文本選擇菜單if(!(dwID == CONTEXT_MENU_TEXTSELECT || dwID == CONTEXT_MENU_CONTROL))result=S_OK;break;case CustomMenu: // 自定義菜單if(dwID!=CONTEXT_MENU_TEXTSELECT)result=OnShowCustomContextMenu(ppt,pcmdtReserved,pdispReserved);break;}return result; } 在CLhpHtmlView中定義的枚舉類型CONTEXT_MENU_MODE舉出了定制右鍵彈出菜單的四種類型
enum CONTEXT_MENU_MODE // 上下文菜單 {NoContextMenu, // 無菜單DefaultMenu, // 默認菜單TextSelectionOnly, // 僅文本選擇菜單CustomMenu // 自定義菜單 }; 通過CLhpHtmlView的函數SetContextMenuMode來設置右鍵菜單的類型。如果設定的右鍵彈出菜單是“自定義菜單”類型, 我們只要在CLhpHtmlView的派生類中重載OnShowCustomContextMenu虛函數即可,如下代碼 CDemoView是CLhpHtmlView的派生類 HRESULT CDemoView::OnShowCustomContextMenu(LPPOINT ppt, LPUNKNOWN pcmdtReserved,LPDISPATCH pdispReserved) {if ((ppt==NULL)||(pcmdtReserved==NULL)||(pcmdtReserved==NULL))return S_OK;HRESULT hr=0;IOleWindow *oleWnd=NULL;hr=pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);if((hr != S_OK)||(oleWnd == NULL))return S_OK;HWND hwnd=NULL;hr=oleWnd->GetWindow(&hwnd);if((hr!=S_OK)||(hwnd==NULL)){oleWnd->Release();return S_OK;}IHTMLElementPtr pElem=NULL;hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)&pElem);if(hr != S_OK){oleWnd->Release();return S_OK;}IHTMLElementPtr pParentElem=NULL;_bstr_t tagID;BOOL go=TRUE;pElem->get_id(&tagID.GetBSTR());while(go && tagID.length()==0){hr=pElem->get_parentElement(&pParentElem);if(hr==S_OK && pParentElem!=NULL){pElem->Release();pElem=pParentElem;pElem->get_id(&tagID.GetBSTR());}elsego=FALSE;};if(tagID.length()==0)tagID="no id";CMenu Menu,SubMenu;Menu.CreatePopupMenu();CString strTagID = ToStr(tagID);if(strTagID == "red")Menu.AppendMenu(MF_BYPOSITION, ID_RED, "您點擊的是紅色");else if(strTagID == "green")Menu.AppendMenu(MF_BYPOSITION, ID_GREEN, "您點擊的是綠色");else if(strTagID == "blue")Menu.AppendMenu(MF_BYPOSITION, ID_BLUE, "您點擊的是藍色");elseMenu.AppendMenu(MF_BYPOSITION, ID_NONE, "你點了也白點,請在指定的地方點擊");int MenuID=Menu.TrackPopupMenu(TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,ppt->x, ppt->y, this);switch(MenuID){case ID_RED:MessageBox("紅色");break;case ID_GREEN:MessageBox("紅色");break;case ID_BLUE:MessageBox("紅色");break;case ID_NONE:MessageBox("haha");break;}oleWnd->Release();pElem->Release();return S_OK; }
實現腳本擴展(很重要的external接口)
在你嵌入了瀏覽器的工程中,如果網頁的腳本中能調用C++代碼,那將是一件很愜意的事情,要實現這種交互,就必須實現腳本擴展。實現腳本擴展就是在程序中實現一個IDispatch接口,通過CHtmlView類的OnGetExternal虛函數返回此接口指針,這樣就可以在腳本中通過window.external.XXX(關鍵字window可以省略)來 引用接口暴露的方法或屬性(XXX為方法或屬性名)。在MFC中從CCmdTarget派生的類都可以實現自動化,而不必在MFC工程中引入繁雜的ATL。從CCmdTarget派生的類實現自動化接口的時候不要忘了在構造函數中調用EnableAutomation函數。
要使虛函數OnGetExternal發揮作用必須在 自定義的控制站點類中實現IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中調用瀏覽器類的OnGetExternal虛函數,我們在瀏覽器類的派生類重載OnGetExternal虛函數, 通過參數lppDispatch返回一個IDispatch指針,這樣腳本中引用window.external時就是引用的返回的接口,參見代碼 HRESULT CDocHostSite::XDocHostUIHandler::GetExternal(IDispatch ** ppDispatch) {METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);return pThis->m_pView->OnGetExternal( ppDispatch ); }CLhpHtmlView::CLhpHtmlView(BOOL isview) {......EnableAutomation();// 允許自動化 }HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch) {*lppDispatch = GetIDispatch(TRUE);// 返回自身的IDispatch接口return S_OK; } 請注意上面代碼中,在OnGetExternal返回的是自身IDispatch接口, 這樣就不比為腳本擴展而另外寫一個從CCmdTarget派生的新類, CLhpHtmlView本身就是從CCmdTarget派生,直接在上面實現接口就是。
下用具體示例來說明怎樣實現腳本擴展
示例會在網頁上點擊一個按鈕而使整個窗口發生抖動
從CLhpHtmlView派生一個類CDemoView,在類中實現IDispatch, 并通過IDispatch暴露方法WobbleWnd --------------------------------------------------------------------------- 文件 DemoView.h --------------------------------------------------------------------------- ....... class CDemoView : public CLhpHtmlView {......DECLARE_DISPATCH_MAP() // 構建dispatch映射表以暴露方法或屬性......void WobbleWnd();// 抖動窗口 };--------------------------------------------------------------------------- 文件 DemoView.cpp ---------------------------------------------------------------------------......// 把成員函數映射到Dispatch映射表中,暴露方法給腳本 BEGIN_DISPATCH_MAP(CDemoView, CLhpHtmlView)DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE) END_DISPATCH_MAP()......void CDemoView::WobbleWnd() {// 在這里實現抖動窗口...... }--------------------------------------------------------------------------- 文件 Demo.htm ---------------------------------------------------------------------------...... οnclick="external.WobbleWnd()" ...... 這里我要介紹一下DISP_FUNCTION宏,它的作用是將一個函數映射到Dispatch映射表中,我們看DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
CDemoView是宿主類名, "WobbleWnd"是暴露給外面的名字(腳本調用時使用的名字), VT_EMPTY是返回值得類型為空,VTS_NONE說明此方法沒有參數,如果要映射的函數有返回值和參數該 如何映射,通過下面舉例來說明 
C++代碼中如何調用網頁腳本中的函數
IHTMLDocument2::scripts屬性表示HTML文檔中所有腳本對象。使用腳本對象的IDispatch接口的GetIDsOfNames方法可以得到腳本函數的 DispID,得到DispID后,使用IDispatch的Invoke函數可以調用對應的腳本函數。CLhpHtmlView提供了方便的調用JavaScript的函數,請參考CLhpHtmlView中有關鍵字“JScript”的代碼。
定制消息框的標題
我們在腳本中調用alert彈出消息框時,消息框的標題是微軟預定義的“Microsoft Internet Explorer”,如下圖:
在自定義的控制站點類中實現IDocHostShowUI接口,在接口的ShowMessage方法中調用瀏覽器的OnShowMessage,我們重載 OnShowMessage虛函數即可定制消息框的標題,實現代碼如下:
// 窗口標題"Microsoft Internet Explorer"的資源標識 #define IDS_MESSAGE_BOX_TITLE 2213 HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,LPOLESTR lpstrText,LPOLESTR lpstrCaption,DWORD dwType,LPOLESTR lpstrHelpFile,DWORD dwHelpContext,LRESULT * plResult) {//載入Shdoclc.dll 和IE消息框標題字符串HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));if (hinstSHDOCLC == NULL)return S_FALSE;CString strBuf,strCaption(lpstrCaption);strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);// 比較IE消息框標題字符串和lpstrCaption// 如果相同,用自定義標題替換if(strBuf==lpstrCaption)strCaption = m_DefaultMsgBoxTitle;// 創建自己的消息框并且顯示*plResult = MessageBox(CString(lpstrText), strCaption, dwType);//卸載Shdoclc.dll并且返回FreeLibrary(hinstSHDOCLC);return S_OK; } 從代碼中可以看到通過設定m_DefaultMsgBoxTitle的值來改變消息寬的標題,修改此值是同過SetDefaultMsgBoxTitle來實現 void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle) {m_DefaultMsgBoxTitle=strTitle; }
怎樣定制、修改瀏覽器向Web服務器發送的HTTP請求頭
在集成了WebBrowser控件的應用中,Web服務器有時可能希望客戶端(瀏覽器)發送的HTTP請求中附帶一些額外的信息或自定義的 HTTP頭字段,這樣就必須在瀏覽器中控制向Web服務器發送的HTTP請求。 下面是捕獲的一個普通的用瀏覽器發送的HTTP請求頭: GET /text7.htm HTTP/1.0 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, \ application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://localhost Accept-Language: en-us User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Poco 0.31; LHP Browser 1.01; \ .NET CLR 1.1.4322) Host: localhost Connection: Keep-AliveCHtmlView的 void Navigate2(LPCTSTR lpszURL,DWORD dwFlags = 0,LPCTSTR lpszTargetFrameName = NULL,LPCTSTR lpszHeaders = NULL,LPVOID lpvPostData = NULL,DWORD dwPostDataLen = 0 ); 函數參數lpszHeaders可以指定HTTP請求頭,示例如下: Navigate2(_T("http://localhost"),NULL,NULL, "MyDefineField: TestValue"); 我們捕獲的HTTP頭如下:
怎樣修改瀏覽器標識
在HTTP請求頭中User-Agent字段表明了瀏覽器的版本以及操作系統的版本等信息。WEB服務器經常需要知道用戶請求頁面時是來自IE還是來自自己的客戶端中的WebBrowser控件, 以便分開處理,而WebBrowser控件向WEB服務器發送的瀏覽器標識(User-Agent字段)跟用IE發送的是一樣的,怎樣區分自己的瀏覽器和IE呢? 微軟沒有提供現成的方法,要自己想法解決。 前面討論的定制HTTP請求頭就是為這一節準備的。 思路是這樣的: 在自己的瀏覽器里處理每一個U頁面請求,把請求頭User-Agent改成自己想要的。 在CHtmlView的OnBeforeNavigate2虛函數里來修改HTTP請求是再好不過了, #define WM_NVTO (WM_USER+1000)class NvToParam { public:CString URL;DWORD Flags;CString TargetFrameName;CByteArray PostedData;CString Headers; };void CDemoView::OnBeforeNavigate2(LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel) {CString strHeaders(lpszHeaders);if(strHeaders.Find("User-Agent:LHPBrowser 1.0") < 0)// 檢查頭里有沒有自定義的User-Agent串{*pbCancel = TRUE;// 沒有,取消這次導航if(!strHeaders.IsEmpty())strHeaders += "\r\n";strHeaders += "User-Agent:LHPBrowser 1.0";// 加上自定義的User-Agent串NvToParam* pNvTo = new NvToParam;pNvTo->URL = lpszURL;pNvTo->Flags = nFlags;pNvTo->TargetFrameName = lpszTargetFrameName;baPostedData.Copy(pNvTo->PostedData);pNvTo->Headers = strHeaders;// 發送一個自定義的導航消息,并把參數發過去PostMessage(WM_NVTO,(WPARAM)pNvTo);return;}CHtmlView::OnBeforeNavigate2(lpszURL, nFlags, lpszTargetFrameName, baPostedData, lpszHeaders, pbCancel); }LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam) {NvToParam* pNvTo = (NvToParam*)wParam;Navigate2((LPCTSTR)pNvTo->URL, pNvTo->Flags, pNvTo->PostedData, (LPCTSTR)pNvTo->TargetFrameName, (LPCTSTR)pNvTo->Headers);delete pNvTo;return 1; } 在OnBeforeNavigate2中如果發現沒有自定義的User-Agent串,就加上這個串,并取消本次導航,再Post一個消息(一定要POST,讓OnBeforeNavigate2跳出以后再進行導航 ),在消息中再次導航,再次導航時請求頭已經有了自己的標識,所以能正常的導航。
去掉討厭的異常警告
在程序中使用了CHtmlView以后,我們在調整窗口大小的時候經常會看到輸出窗口輸出的異常警告: ReusingBrowser.exe 中的 0x77e53887 處最可能的異常: Microsoft C++ exception: COleException @ 0x0012e348 。 Warning: constructing COleException, scode = DISP_E_MEMBERNOTFOUND($80020003). 這是由于CHtmlView在處理WM_SIZE消息時的一點小問題引起的,采用如下代碼處理WM_SIZE消息就不會有此警告了 void CLhpHtmlView::OnSize(UINT nType, int cx, int cy) {CFormView::OnSize(nType, cx, cy);if (::IsWindow(m_wndBrowser.m_hWnd)) { CRect rect; GetClientRect(rect); // 就這一句與CHtmlView的不同::AdjustWindowRectEx(rect, GetStyle(), FALSE, WS_EX_CLIENTEDGE);m_wndBrowser.SetWindowPos(NULL, rect.left, rect.top, rect.Width(), rect.Height(), SWP_NOACTIVATE | SWP_NOZORDER); } }
怎樣處理瀏覽器內的拖放
有時可能有這樣的需求,我們希望在資源管理器里托一個文件到瀏覽器而做出相應的處理,甚至是將文件拖到某一個網頁元素上來做出相應的處理,而瀏覽器默認的處理拖放文件操作是將文件打開,但WebBrowser控件給了我們一個自己處理拖放的機會。 那就是在自定義的控制站點類中實現IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中調用 瀏覽器類的OnGetDropTarget虛函數。要處理網頁內的拖放,必需在OnGetDropTarget函數中返回一個自己定義的IDropTarget接口指針, 所以我們自己寫一個類CMyOleDropTarget從COleDropTarget類派生,并且在實現IDropTarget接口,此類的代碼在這就不列出了,請下載演示 程序,參考文件MyOleDropTarget.h和MyOleDropTarget.cpp。我們看CLhpHtmlView中OnGetDropTarget的代碼 HRESULT CLhpHtmlView::OnGetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget ) {m_DropTarget.SetIEDropTarget(pDropTarget);LPDROPTARGET pMyDropTarget;pMyDropTarget = (LPDROPTARGET)m_DropTarget.GetInterface(&IID_IDropTarget);if(pMyDropTarget){*ppDropTarget = pMyDropTarget;pMyDropTarget->AddRef();return S_OK;}return S_FALSE; } m_DropTarget即為自定義的處理拖放的對象。這樣就能通過在從CLhpHtmlView派生的類中重載OnDragEnter、OnDragOver、 OnDrop、OnDragLeave虛函數來處理拖放了。在這里順帶講一下視圖是怎樣處理拖放的。 要使視圖處理拖放,首先在視圖里添加一個COleDropTarget(或派生類)成員變量,如CLhpHtmlView中的“CMyOleDropTarget m_DropTarget;”,再在 視圖創建時調用COleDropTarget對象的Register,即把視圖與COleDropTarget對象關聯起來,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再對拖放 觸發的事件進行相應的處理, OnDragEnter 把某對象拖入到視圖時觸發,在此檢測拖入的對象是不是視圖想接受的對象,如是返回“DROPEFFECT_MOVE”表示接受此對象,如 if(pDataObject->IsDataAvailable(CF_HDROP))// 被拖對象是文件嗎?return DROPEFFECT_MOVE; OnDragOver 被拖對象在視圖上移動,同OnDragEnter一樣檢測拖入對象,如果要接受此對象返回“DROPEFFECT_MOVE”。 OnDrop 拖著被拖對象在視圖上放開鼠標,在這里對拖入對象做出處理; OnDragLeave 拖著被拖對象離開視圖。 C++的代碼寫好了,但事情還沒完,還必須在網頁里用腳本對拖放事件進行處理, 即頁面里哪個元素要接受拖放對象哪個元素就要處理ondragenter、ondragover、ondrop,代碼其實很簡單,讓事件的返回值為false即可,這樣 C++的代碼才有機會處理拖放事件,代碼如下: ...... <td οndragenter="event.returnValue = false" οndragοver="event.returnValue = false" \ οndrοp="event.returnValue = false"> ...... 如果要使整個視圖都接受拖放,則在Body元素中處理此三個事件。 注意:別忘了讓工程對OLE的支持即在初始化應用程序時調用AfxOleInit()。
怎樣禁止網頁元素的選取
用網頁做界面時多數情況下是不希望網頁上的元素是能夠被鼠標選中的, 要使網頁元素不能被選中做法是:給瀏覽器的“宿主信息標記”加上DOCHOSTUIFLAG_DIALOG標記。
“宿主信息標記”用N個標記位來控制瀏覽器的許多性質,如:
- 禁用瀏覽器的3D的邊緣;
- 禁止滾動條;
- 禁用腳本;
- 定義雙擊處理的方式;
- 禁用瀏覽器的自動完成功能;
...... 更多詳情請參考MSDN的DOCHOSTUIFLAG幫助。
怎樣修改“宿主信息標記”?
在CDocHostSite中實現IDocHostUIHandler, 在GetHostInfo方法中調用瀏覽器的OnGetHostInfo虛函數,在虛函數OnGetHostInfo中便可指定“宿主信息標記”,如: 
其它
在CLhpHtmlView中還提供了幾個函數, 修改網頁元素的內容:BOOL PutElementHtml(CString ElemID,CString Html); 取表單元素的值:BOOL GetElementValue(CString ElemID,CString& Value); 設置表單元素的值:BOOL PutElementValue(CString ElemID,CString Value); 給表單元素設置焦點:void ElementSetFocus(CString EleName);
轉載于:https://www.cnblogs.com/cdo/archive/2005/06/24/180026.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
 
                            
                        - 上一篇: 【亲测】2022最新H5手机微商城运营源
- 下一篇: 晁錯論
