关键字:“添加到收藏夹”对话框, 模态窗口,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内置的COM自动化对象,以实现对文档对象模型(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 编程简述(四)“添加到收藏夹”对话框

Internet Explorer 编程简述(四)“添加到收藏夹”对话框相关推荐

  1. Internet Explorer 编程简述(序)

    一直对Microsoft Internet Explorer编程非常感兴趣,曾花了不少时间琢磨,也与众多网友讨论过问题,2000年将心得写成一篇<TWebBrowser编程简述>,发表在自 ...

  2. mac php怎么做网页,Mac_mac系统中safari怎么添加书签? 把常用网页添加到收藏夹的效果,苹果电脑mac系统自带safari浏览 - phpStudy...

    mac系统中safari怎么添加书签? 把常用网页添加到收藏夹的效果 苹果电脑mac系统自带safari浏览器与普通的win系统是不一样的,收藏常用的网页也是不一样的,下面是将你常用的网页加入到书签里 ...

  3. ubuntu创建快捷方式和添加到收藏夹

    以Eclipse为例子,其他软件同理,自己的用户名cdyang 1.将Eclipse解压到/home/cdyang/app/eclipse下 2.打开终端,cd到自己桌面 cd /home/cdyan ...

  4. 设为主页代码及添加到收藏夹代码大全 1

    < a  href ='#'  onclick ='window.external.AddFavorite("http://dotnet.aspx.cc/","[孟 ...

  5. 添加到收藏夹和设置首页代码大全

    < HTML >      < HEAD >          < title > 添加到收藏夹和设置首页代码大全 </ title >         ...

  6. Ubuntu中将Eclipse添加到收藏夹

    a@DataServer:~$ sudo gedit /usr/share/applications/pycharm.desktop 写入 [Desktop Entry] Name = Eclipse ...

  7. IntelliJ IDEA for Mac在MacOS模式下添加到收藏夹的快捷键(Add Favorite Shortcut)

    快捷键 快捷键符号 英文名称 功能说明 Option + Shift + F ⌥⇧F Add Favorite 添加到收藏夹 添加到收藏夹后,你可以按下组合键 Command + 2 打开收藏夹窗格, ...

  8. 将网页添加至收藏夹代码

    <a href="javascript:void(0);" οnclick="addFavorite();" rel="sidebar" ...

  9. 火狐怎么导入收藏夹_火狐浏览器怎么把网页添加到收藏夹

    很多人上网时候看到有好看或者有用的网页,都会收藏或者保存下来以备用,那么火狐浏览器该怎么保存网页呢?针对这个问题呢,接下来小编和大家分享火狐浏览器保存网页的方法.火狐浏览器保存网页的方法:1.首先我们 ...

最新文章

  1. 五年磨砺:微软Vista开发过程全记录
  2. UA MATH563 概率论的数学基础 鞅论初步6 鞅的性质 鞅差序列
  3. 浅析TCP之SACK(选择性确认)
  4. 某些情况下安卓引入so冲突的解决
  5. 增加XP的IIS连接数,解决403.9连接用户过多的问题
  6. Table中Family和Qualifier的关系与区别(转载)
  7. HTTP/2 协议入门
  8. 【JAVA 第五章 】课后习题 Vector类的 初使用
  9. Python Seaborn教程
  10. 纽微特成立起因:申某账务有鬼,张某不干活怎么不说
  11. 如何判断企业微信是否在线?
  12. 怎么把u盘做成启动盘装系统?
  13. 中央气象台气象监测数据爬取Python实战分析
  14. 数学专业英语--极限
  15. [译] APT分析报告:06.猖獗的小猫——针对伊朗的APT攻击活动详解
  16. 债券融资和股权融资区别,债券融资的优缺点是什么
  17. 从安防监控走进手机VR 红外线LED翻身
  18. 计算机术语 日语,常用日语计算机词汇~~
  19. CSDN--在有序和无序段落中如何换行
  20. 分析洋葱模型实现原理,在自己项目中接入洋葱模型

热门文章

  1. hbase和couchdb_使用CouchDB和Groovy的RESTClient进行REST
  2. linux c 编译器 macs,【MACS】MACS(Model-based Analysis of ChIP-Seq)使用说明
  3. BES的ANC之FF,FB,MC功能
  4. 小白教程-JBoss的下载以及安装、部署
  5. rocketmq源码分析之namesrv路由中心
  6. Spring Boot学习笔记----mybatis注解(一)
  7. java连连看游戏设计与实现
  8. 乾坤未定,你我皆是黑马!
  9. AVAWEB 华为商城购物系统 servlet+jsp 源码
  10. maven获得dom4j_关于dom4j在maven中的使用