Internet Explorer 編程簡述(四)“添加到收藏夾”對話方塊

來源:互聯網
上載者:User

關鍵字:“添加到收藏夾”對話方塊, 模態視窗,IShellUIHelper,DoAddToFavDlg, DoOrganizeFavDlg

1、概述

調用“添加到收藏夾”對話方塊(如下)與調用“整理收藏夾”對話方塊有不同之處,前者所做的工作比後者要來得複雜。將連結添加到收藏夾除了將連結儲存之外,還可能會有離線訪問的設定,從IE 4.0到IE 5.0,處理的方式也發生了一些變化。

 

2、IShellUIHelper介面

微軟專門提供了一個介面IShellUIHelper來實現對Windows Shell API一些功能的訪問,將連結添加到收藏夾也是其中之一,就是下面的AddFavorite函數。

HRESULT IShellUIHelper::AddFavorite(BSTR URL, VARIANT *Title);

執行個體代碼如下:

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”函數並沒有像“DoOrganizeFavDlg”那樣需要一個父視窗控制代碼。這也導致與在IE中開啟不同,通過IShellUIHelper介面顯示出來的“添加到收藏夾”對話方塊是“非模態”的,有一個獨立於我們應用程式的工作列按鈕,這使我們的瀏覽器顯得非常不專業(我是個追求完美的人,這也是我的瀏覽器遲遲不能發布的原因之一)。
於是我們很自然地想到“shdocvw.dll”中除了“DoOrganizeFavDlg”外,應該還有一個類似的函數,可以傳入一個父視窗控制代碼用以顯示模態視窗,也許就像這樣:

typedef UINT (CALLBACK* LPFNADDFAV)(HWND, LPTSTR, LPTSTR);

事實上,這樣的函數確實存在於“shdocvw.dll”中,那就是“DoAddToFavDlg”。

3、DoAddToFavDlg函數

“DoAddToFavDlg”函數也是“shdocvw.dll”暴露出來的函數之一,其原型如下:

typedef BOOL (CALLBACK* LPFNADDFAV)(HWND, TCHAR*, UINT, TCHAR*, UINT,LPITEMIDLIST);

第一個參數正是我們想要的父視窗控制代碼,第二和第四個參數分別是初始目錄(一般來說就是收藏夾目錄)和要添加的連結的名字(比如網頁的Title),第三和第五個參數分別是第二和第四兩個緩衝區的長度,而最後一個參數則是指向與第二個參數目錄相關的item identifier list的指標(PIDL)。但最奇怪的是這裡並沒有像“AddFavorite”函數一樣的連結URL,那連結是怎樣添加的呢?答案是“手動建立”。
第二個參數在函數調用返回後會包含使用者在“添加到收藏夾”對話方塊中選擇或建立的完整連結路徑名(如“X:/XXX/mylink.url”),我們就根據這個路徑和網頁的URL來建立連結,代碼如下(為簡化,此處省去檢查"shdocvw.dll"是否已在記憶體中的代碼,參見《Internet Explorer 編程簡述(三)“整理收藏夾”對話方塊》):

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, "");  //建立Internet捷徑
      }
    }
    FreeLibrary(hMod);
  }
  return;
}

實現CreateInternetShortcut函數建立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;
}

好,上面的方法雖然麻煩一點,但總算解決了“模態視窗”的問題,使得我們的程式不至於讓使用者鄙視。但是問題又來了,我們發現“允許離線使用”是Disabled的,那“自訂”也就無從談起了,儘管90%的人都沒有使用過IE提供的離線瀏覽。

難道我們的希望要破滅嗎?我們一方面想像調用“AddFavorite”函數一樣的不必手動建立連結,一方面又要模態顯示視窗,就像IE那樣,還能自訂離線瀏覽。

4、指令碼方式

許多網頁上都會有一個按鈕或連結“添加本頁到收藏夾”,實際上通過下面的指令碼顯示模態的“添加到收藏夾”對話方塊將網頁加入到收藏夾。

window.external.AddFavorite(location.href, document.title);

這裡的external對象是WebBrowser內建的COMAutomation 物件,以實現對文件物件模型(DOM)的擴充(我們也可以通過IDocHostUIHandler實現自己的擴充).查閱MSDN可以得知external對象的的方法與IShellUIHelper介面提供的方法是一樣的。我們有理由相信,IShellUIHelper提供了對WebBrowser內建的external對象的訪問,如果在適當的地方建立IShellUIHelper介面的執行個體,也許調用“AddFavorite”函數顯示出來的就是模態對話方塊了。問題是我們還沒有找到這樣的地方。

從上面的指令碼,我們很自然地又想到另一個方法。如果能夠讓網頁來執行上面的指令碼,豈不是問題就解決了?說做就做,如下:

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   pHTMLDoc = (IHTMLDocument2*)GetHtmlDocument();
  if ( pHTMLDoc != NULL  )
  {
    CComQIPtr   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對象的指標,再調用其方法execScript來執行指令碼(事實上可以執行任意的指令碼)。實驗發現,這個方法非常有效,不僅視窗是模態的,而且不需要手動建立連結,更重要的是“允許離線使用”和“自訂”按鈕也可以用了。

5、問題仍舊沒有解決

執行指令碼的方式看起來有效,可一旦我們的程式實現了IDocHostUIHandler介面對WebBrowser進行進階控制,就會發現一旦執行的指令碼包含有對“external”對象的調用,就會出現“缺少對象”的指令碼錯誤。原因是當MSHTML解析引擎(並非WebBrowser)檢查到宿主實現了IDocHostUIHandler介面,就會調用其GetExternal方法以獲得一個用以擴充DOM的自動化介面的引用。

HRESULT IDocHostUIHandler::GetExternal(IDispatch **ppDispatch)

但有時候我們並沒有想要擴充DOM,同時我們還希望WebBrowser使用它自己的DOM擴充。糟糕的是GetExternal方法的文檔中說這種情況下必須把ppDispatch設定為NULL,換句話說,WebBrowser連它內建的external對象也不用了,那我們的window.external.AddFavorite就變得無處為家了。

我曾多方嘗試將WebBrowser內建的external對象找出來,雖然都沒有成功,但是解決問題的方法卻被我找到了。

6、完美的方案

WebBrowser內建的external對象我們雖然找不到,但它肯定存在,我們只要想辦法讓WebBrowser自己完成對其調用即可。實現非常簡單,找到WebBrowser中包含的“Internet Explorer_Server”視窗的控制代碼,發一個訊息就完成了。下面的代碼中假設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中選擇“添加到收藏夾”的效果一模一樣。

至於為什麼這樣做,後續文章再說。

參考資料
MSDN: Adding Internet Explorer Favorites to Your Application
MSDN: IShellUIHelper Interface
MSDN: external Object
MSDN: IDocHostUIHandler Interface

引用地址:Internet Explorer 編程簡述(四)“添加到收藏夾”對話方塊

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.