1、概述
調(diào)用“添加到收藏夾”對(duì)話框(如下)與調(diào)用“整理收藏夾”對(duì)話框有不同之處,前者所做的工作比后者要來(lái)得復(fù)雜。將鏈接添加到收藏夾除了將鏈接保存之外,還可能會(huì)有脫機(jī)訪問(wèn)的設(shè)置,從IE 4.0到IE 5.0,處理的方式也發(fā)生了一些變化。


2、IShellUIHelper接口
微軟專門提供了一個(gè)接口IShellUIHelper來(lái)實(shí)現(xiàn)對(duì)Windows Shell API一些功能的訪問(wèn),將鏈接添加到收藏夾也是其中之一,就是下面的AddFavorite函數(shù)。
|
HRESULT IShellUIHelper::AddFavorite(BSTR URL, VARIANT *Title);
|
實(shí)例代碼如下:
|
void CMyHtmlView::OnAddToFavorites() { IShellUIHelper* pShellUIHelper; HRESULT hr = CoCreateInstance(CLSID_ShellUIHelper, NULL, CLSCTX_INPROC_SERVER, IID_IShellUIHelper,(LPVOID*)&pShellUIHelper);
if (SUCCEEDED(hr)) { _variant_t vtTitle(GetTitle().AllocSysString()); CString strURL = m_webBrowser.GetLocationURL();
pShellUIHelper->AddFavorite(strURL.AllocSysString(), &vtTitle); pShellUIHelper->Release(); } }
|
我們注意到這里的“AddFavorite”函數(shù)并沒(méi)有像“DoOrganizeFavDlg”那樣需要一個(gè)父窗口句柄。這也導(dǎo)致與在IE中打
開不同,通過(guò)IShellUIHelper接口顯示出來(lái)的“添加到收藏夾”對(duì)話框是“非模態(tài)”的,有一個(gè)獨(dú)立于我們應(yīng)用程序的任務(wù)欄按鈕,這使我們的瀏覽
器顯得非常不專業(yè)(我是個(gè)追求完美的人,這也是我的瀏覽器遲遲不能發(fā)布的原因之一)。
于是我們很自然地想到“shdocvw.dll”中除了“DoOrganizeFavDlg”外,應(yīng)該還有一個(gè)類似的函數(shù),可以傳入一個(gè)父窗口句柄用以顯示模態(tài)窗口,也許就像這樣:
|
typedef UINT (CALLBACK* LPFNADDFAV)(HWND, LPTSTR, LPTSTR);
|
事實(shí)上,這樣的函數(shù)確實(shí)存在于“shdocvw.dll”中,那就是“DoAddToFavDlg”。
3、DoAddToFavDlg函數(shù)
“DoAddToFavDlg”函數(shù)也是“shdocvw.dll”暴露出來(lái)的函數(shù)之一,其原型如下:
|
typedef BOOL (CALLBACK* LPFNADDFAV)(HWND, TCHAR*, UINT, TCHAR*, UINT,LPITEMIDLIST);
|
第一個(gè)參數(shù)正是我們想要的父窗口句柄,第二和第四個(gè)參數(shù)分別是初始目錄(一般來(lái)說(shuō)就是收藏夾目錄)和要添加的鏈接的名字(比如網(wǎng)頁(yè)的
Title),第三和第五個(gè)參數(shù)分別是第二和第四兩個(gè)緩沖區(qū)的長(zhǎng)度,而最后一個(gè)參數(shù)則是指向與第二個(gè)參數(shù)目錄相關(guān)的item identifier
list的指針(PIDL)。但最奇怪的是這里并沒(méi)有像“AddFavorite”函數(shù)一樣的鏈接URL,那鏈接是怎樣添加的呢?答案是“手動(dòng)創(chuàng)建”。
第二個(gè)參數(shù)在函數(shù)調(diào)用返回后會(huì)包含用戶在“添加到收藏夾”對(duì)話框中選擇或創(chuàng)建的完整鏈接路徑名(如“X:\XXX\mylink.url”),我們就根
據(jù)這個(gè)路徑和網(wǎng)頁(yè)的URL來(lái)創(chuàng)建鏈接,代碼如下(為簡(jiǎn)化,此處省去檢查"shdocvw.dll"是否已在內(nèi)存中的代碼,參見(jiàn)《Internet
Explorer 編程簡(jiǎn)述(三)“整理收藏夾”對(duì)話框》):
|
void CMyHtmlView::OnFavAddtofav() { typedef BOOL (CALLBACK* LPFNADDFAV)(HWND, TCHAR*, UINT, TCHAR*, UINT,LPITEMIDLIST);
HMODULE hMod = (HMODULE)LoadLibrary("shdocvw.dll"); if (hMod) { LPFNADDFAV lpfnDoAddToFavDlg = (LPFNADDFAV)GetProcAddress( hMod, "DoAddToFavDlg"); if (lpfnDoAddToFavDlg) { TCHAR szPath[MAX_PATH]; LPITEMIDLIST pidlFavorites;
if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_FAVORITES, TRUE) && (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFavorites)))) { TCHAR szTitle[MAX_PATH]; strcpy(szTitle, GetLocationName());
TCHAR szURL[MAX_PATH]; strcpy(szURL, GetLocationURL());
BOOL bOK = lpfnDoAddToFavDlg(m_hWnd, szPath, sizeof(szPath)/sizeof(szPath[0]), szTitle, sizeof(szTitle)/sizeof(szTitle[0]), pidlFavorites); CoTaskMemFree(pidlFavorites);
if (bOK) CreateInternetShortcut( szURL, szPath, ""); //創(chuàng)建Internet快捷方式 } } FreeLibrary(hMod); } return; }
|
實(shí)現(xiàn)CreateInternetShortcut函數(shù)創(chuàng)建Internet快捷方式,可以用讀寫INI文件的方法,但更好的則是利用IUniformResourceLocator接口。
|
HRESULT CMyHtmlView::CreateInternetShortcut(LPCSTR pszURL, LPCSTR pszURLfilename, LPCSTR szDescription,LPCTSTR szIconFile,int nIndex) { HRESULT hres;
CoInitialize(NULL);
IUniformResourceLocator *pHook;
hres = CoCreateInstance (CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, IID_IUniformResourceLocator, (void **)&pHook);
if (SUCCEEDED (hres)) { IPersistFile *ppf; IShellLink *psl;
// Query IShellLink for the IPersistFile interface for hres = pHook->QueryInterface (IID_IPersistFile, (void **)&ppf); hres = pHook->QueryInterface (IID_IShellLink, (void **)&psl);
if (SUCCEEDED (hres)) { WORD wsz [MAX_PATH]; // buffer for Unicode string
// Set the path to the shortcut target. pHook->SetURL(pszURL,0);
hres = psl->SetIconLocation(szIconFile,nIndex);
if (SUCCEEDED (hres)) { // Set the description of the shortcut. hres = psl->SetDescription (szDescription);
if (SUCCEEDED (hres)) { // Ensure that the string consists of ANSI characters. MultiByteToWideChar (CP_ACP, 0, pszURLfilename, -1, wsz, MAX_PATH);
// Save the shortcut via the IPersistFile::Save member function. hres = ppf->Save (wsz, TRUE); } }
// Release the pointer to IPersistFile. ppf->Release (); psl->Release (); }
// Release the pointer to IShellLink. pHook->Release ();
} return hres; }
|
好,上面的方法雖然麻煩一點(diǎn),但總算解決了“模態(tài)窗口”的問(wèn)題,使得我們的程序不至于讓用戶鄙視。但是問(wèn)題又來(lái)了,我們發(fā)現(xiàn)“允許脫機(jī)使用”是Disabled的,那“自定義”也就無(wú)從談起了,盡管90%的人都沒(méi)有使用過(guò)IE提供的脫機(jī)瀏覽。

難道我們的希望要破滅嗎?我們一方面想像調(diào)用“AddFavorite”函數(shù)一樣的不必手動(dòng)創(chuàng)建鏈接,一方面又要模態(tài)顯示窗口,就像IE那樣,還能自定義脫機(jī)瀏覽。
3、腳本方式
許多網(wǎng)頁(yè)上都會(huì)有一個(gè)按鈕或鏈接“添加本頁(yè)到收藏夾”,實(shí)際上通過(guò)下面的腳本顯示模態(tài)的“添加到收藏夾”對(duì)話框?qū)⒕W(wǎng)頁(yè)加入到收藏夾。
|
window.external.AddFavorite(location.href, document.title);
|
這里的external對(duì)象是WebBrowser內(nèi)置的COM自動(dòng)化對(duì)象,以實(shí)現(xiàn)對(duì)文檔對(duì)象模型(DOM)的擴(kuò)展(我們也可以通過(guò)
IDocHostUIHandler實(shí)現(xiàn)自己的擴(kuò)展).查閱MSDN可以得知external對(duì)象的的方法與IShellUIHelper接口提供的方法
是一樣的。我們有理由相信,IShellUIHelper提供了對(duì)WebBrowser內(nèi)置的external對(duì)象的訪問(wèn),如果在適當(dāng)?shù)牡胤絼?chuàng)建
IShellUIHelper接口的實(shí)例,也許調(diào)用“AddFavorite”函數(shù)顯示出來(lái)的就是模態(tài)對(duì)話框了。問(wèn)題是我們還沒(méi)有找到這樣的地方。
從上面的腳本,我們很自然地又想到另一個(gè)方法。如果能夠讓網(wǎng)頁(yè)來(lái)執(zhí)行上面的腳本,豈不是問(wèn)題就解決了?說(shuō)做就做,如下:
|
void CMyHtmlView::OnFavAddtofav() { CString strUrl = GetLocationURL(); CString strTitle = GetLocationName(); CString strjs = "javascript:window.external.AddFavorite('" + strUrl + "'," + "'" + strTitle + "');"; ExecScript(strjs); }
void CMIEView::ExecScript(CString strjs) { CComQIPtr<IHTMLDocument2> pHTMLDoc = (IHTMLDocument2*)GetHtmlDocument(); if ( pHTMLDoc != NULL ) { CComQIPtr<IHTMLWindow2> pHTMLWnd; pHTMLDoc->get_parentWindow( &pHTMLWnd );
if ( pHTMLWnd != NULL ) { CComBSTR bstrjs = strjs.AllocSysString(); CComBSTR bstrlan = SysAllocString(L"javascript"); VARIANT varRet; pHTMLWnd->execScript(bstrjs, bstrlan, &varRet); } } }
|
先從CHtmlView獲得文檔的父窗口window對(duì)象的指針,再調(diào)用其方法execScript來(lái)執(zhí)行腳本(事實(shí)上可以執(zhí)行任意的腳本)。
試驗(yàn)發(fā)現(xiàn),這個(gè)方法非常有效,不僅窗口是模態(tài)的,而且不需要手動(dòng)創(chuàng)建鏈接,更重要的是“允許脫機(jī)使用”和“自定義”按鈕也可以用了。
4、問(wèn)題仍舊沒(méi)有解決
執(zhí)行腳本的方式看起來(lái)有效,可一旦我們的程序?qū)崿F(xiàn)了IDocHostUIHandler接口對(duì)WebBrowser進(jìn)行高級(jí)控制,就會(huì)發(fā)現(xiàn)一旦
執(zhí)行的腳本包含有對(duì)“external”對(duì)象的調(diào)用,就會(huì)出現(xiàn)“找不到對(duì)象”的腳本錯(cuò)誤。原因是當(dāng)MSHTML解析引擎(并非WebBrowser)檢查
到宿主實(shí)現(xiàn)了IDocHostUIHandler接口,就會(huì)調(diào)用其GetExternal方法以獲得一個(gè)用以擴(kuò)展DOM的自動(dòng)化接口的引用。
|
HRESULT IDocHostUIHandler::GetExternal(IDispatch **ppDispatch)
|
但有時(shí)候我們并沒(méi)有想要擴(kuò)展DOM,同時(shí)我們還希望WebBrowser使用它自己的DOM擴(kuò)展。糟糕的是GetExternal方法的文檔中
說(shuō)這種情況下必須把ppDispatch設(shè)置為NULL,換句話說(shuō),WebBrowser連它內(nèi)置的external對(duì)象也不用了,那我們的
window.external.AddFavorite就變得無(wú)處為家了。
我曾多方嘗試將WebBrowser內(nèi)置的external對(duì)象找出來(lái),雖然都沒(méi)有成功,但是解決問(wèn)題的方法卻被我找到了。
5、完美的方案
WebBrowser內(nèi)置的external對(duì)象我們雖然找不到,但它肯定存在,我們只要想辦法讓W(xué)ebBrowser自己完成對(duì)其調(diào)用即可。
實(shí)現(xiàn)非常簡(jiǎn)單,找到WebBrowser中包含的“Internet
Explorer_Server”窗口的句柄,發(fā)一個(gè)消息就完成了。下面的代碼中假設(shè)m_hWndIE就是“Internet
Explorer_Server”窗口的句柄。
|
#define ID_IE_ID_ADDFAV 2261 ::SendMessage( m_hWndIE, WM_COMMAND, MAKEWPARAM(LOWORD(ID_IE_ID_ADDFAV), 0x0), 0 );
|
試一試成果,是不是和在Internet Explorer中選擇“添加到收藏夾”的效果一模一樣。
至于為什么這樣做,后續(xù)文章再說(shuō)。