关键字:add to favorite, import/export wizard, shell docobject view, internet explorer_server
1、概述
除了“整理收藏夹”和“添加到收藏夹”对话框外,还有其它一些对话框是我们希望直接通过webbrowser调用的,比如“导入/导出”对话框,用一般的方法很难调用。ishelluihelper尽管提供了importexportfavorites方法,但结果只是显示一个选择文件的对话框,且只能导入/导出收藏夹而不能对cookies操作。
2、契机
msdn中有一篇叫“webbrowser customization”的文章,其中介绍了通过idochostuihandler.showcontextmenu方法自定义webbrowser上下文菜单的方法。其原理是从“shdoclc.dll”的资源中创建菜单,作一些修改之后用trackpopupmenu函数(注意在标志中包含tpm_returncmd)将菜单弹出,然后把返回的command id发送给“internet explorer_server”窗口进行处理。
……
// 显示菜单
int iselection = ::trackpopupmenu(hmenu,
? tpm_leftalign | tpm_rightbutton | tpm_returncmd,
? ppt->x,
? ppt->y,
? 0,
? hwnd,
? (rect*)null);
// 发送command id到外壳窗口
lresult lr = ::sendmessage(hwnd, wm_command, iselection, null);
……
好,如果找到所有上下文菜单的command id,不就可以随时调用了?确实是这样的。
3、实现
用exescope之类应用程序资源探索器打开“shdoclc.dll”便可以在菜单资源下找到上下文菜单的设计,如下图:
我们要做的,就是将这些id发送到“internet explorer_server”窗口进行处理。问题是webbrowser其实是一个ole容器,我们使用的chtmlview又是更外层的封装,他们的m_hwnd成员变量并不是ie窗口的句柄,如何找到我们需要的句柄呢?请看下面的图:
根据图中显示的从属关系,顺藤摸瓜,最内层的窗口“internet explorer_server”的句柄就是我们需要的东西。为了简化问题,我这里使用了来自msdn magazine资深专栏撰稿人paul dilascia的cfindwnd类,非常好用。
////////////////////////////////////////////////////////////////
// msdn magazine — august 2003
// if this code works, it was written by paul dilascia.
// if not, i dont know who wrote it.
// compiles with visual studio .net on windows xp. tab size=3.
//
// —
// this class encapsulates the process of finding a window with a given class name
// as a descendant of a given window. to use it, instantiate like so:
//
// cfindwnd fw(hwndparent,classname);
//
// fw.m_hwnd will be the hwnd of the desired window, if found.
//
class cfindwnd {
private:
? //////////////////
? // this private function is used with enumchildwindows to find the child
? // with a given class name. returns false if found (to stop enumerating).
? //
? static bool callback findchildclasshwnd(hwnd hwndparent, lparam lparam) {
??? cfindwnd *pfw = (cfindwnd*)lparam;
??? hwnd hwnd = findwindowex(hwndparent, null, pfw->m_classname, null);
??? if (hwnd) {
????? pfw->m_hwnd = hwnd; // found: save it
????? return false; // stop enumerating
??? }
??? enumchildwindows(hwndparent, findchildclasshwnd, lparam); // recurse
??? return true; // keep looking
}
public:
? lpcstr m_classname; // class name to look for
? hwnd m_hwnd; // hwnd if found
? // ctor does the work–just instantiate and go
? cfindwnd(hwnd hwndparent, lpcstr classname)
??? : m_hwnd(null), m_classname(classname)
? {
??? findchildclasshwnd(hwndparent, (lparam)this);
? }
};
再写一个函数invokeieservercommand,调用就很方便了,《internet explorer 编程简述(四)“添加到收藏夹”对话框》中最后给出的方法就是从这里来的。
void cmyhtmlview::invokeieservercommand(int nid)
{
? cfindwnd findiewnd( m_wndbrowser.m_hwnd, "internet explorer_server");
? ::sendmessage( findiewnd.m_hwnd, wm_command, makewparam(loword(nid), 0x0), 0 );
}
void cmyhtmlview::onfavaddtofav()
{
? invokeieservercommand(id_ie_contextmenu_addfav);//调用“添加到收藏夹”对话框
}
4、command ids
对所有的command id逐一尝试后我们发现:
1)不是所有的command id都可以用上面的方法调用;
2)不是所有的command id都是由“internet explorer_server”窗口处理;
3)有一些command id是由上一级窗口“shell docobject view”处理。
所以我们还需要写一个函数。
void cmyhtmlview::invokeshelldocobjcommand(int nid)
{
? cfindwnd findiewnd( m_wndbrowser.m_hwnd, "shell docobject view");
? ::sendmessage( findiewnd.m_hwnd, wm_command, makewparam(loword(nid), 0x0), 0 );
}
调用文章开头提到的“导入/导出”对话框可以这样来做:
void cdemoview::onimportexport()
{
? invokeshelldocobjcommand(id_ie_file_importexport);//调用“导入/导出”对话框
}
由"internet explorer_server"窗口处理的command id:
#define id_ie_contextmenu_addfav 2261
#define id_ie_contextmenu_viewsource 2139
#define id_ie_contextmenu_refresh 6042
由"shell docobject view"窗口处理的command id:
#define id_ie_file_saveas 258
#define id_ie_file_pagesetup 259
#define id_ie_file_print 260
#define id_ie_file_newwindow 275
#define id_ie_file_printpreview 277
#define id_ie_file_newmail 279
#define id_ie_file_senddesktopshortcut 284
#define id_ie_help_aboutie 336
#define id_ie_help_helpindex 337
#define id_ie_help_webtutorial 338
#define id_ie_help_freestuff 341
#define id_ie_help_productupdate 342
#define id_ie_help_faq 343
#define id_ie_help_onlinesupport 344
#define id_ie_help_feedback 345
#define id_ie_help_bestpage 346
#define id_ie_help_searchweb 347
#define id_ie_help_mshome 348
#define id_ie_help_visitinternet 349
#define id_ie_help_startpage 350
#define id_ie_file_importexport 374
#define id_ie_file_addtrust 376
#define id_ie_file_addlocal 377
#define id_ie_file_newpublishinfo 387
#define id_ie_file_newcorrespondent 390
#define id_ie_file_newcall 395
#define id_ie_help_netscapeuser 351
#define id_ie_help_enhancedsecurity 375
5、refresh
熟悉tembeddedwb的读者可能注意到了id_ie_contextmenu_refresh(6042)这个id,在tembeddedwb中给出了一个当网页刷新时触发的onrefresh事件,其中的关键代码如下:
……
if assigned(fonrefresh) and ((ncmdid = 6041 {f5}) or (ncmdid = 6042 {contextmenu}) or (ncmdid = 2300)) then
begin
? fcancel := false;
? fonrefresh(self, ncmdid, fcancel);
? if fcancel then result := s_ok;
end;
……
其中的6402就是我们这里的id_ie_contextmenu_refresh,2300是内置的刷新命令,那6041呢。见下图,还是“shdoclc.dll”,6041原来是ie“查看”菜单下“刷新”菜单的命令id。实际开发中我们发现直接调用webbrowser的refresh命令有时候会导致一些错误,可以用这里的方法替换一下。
6、需要注意的问题
1)用invokeieservercommand(id_ie_contextmenu_addfav)调用“添加到收藏夹”对话框时需要注意的是,ie接收到id_ie_contextmenu_addfav命令时是对网页中当前被选中的链接来执行“添加到收藏夹”操作的,如果没有选中的链接,才是将当前网页添加到收藏夹。
2)新建ie窗口。这是浏览器编程中的难题之一,即从当前窗口新建一个internet explorer窗口,完全复制当前页的内容(包括“前进”、“后退”的状态),这可以通过invokeshelldocobjcommand(id_ie_file_newwindow)来实现。
3)显示ie的版本信息。调用invokeshelldocobjcommand(id_ie_help_aboutie),如下:
4)invokeshelldocobjcommand(id_ie_file_print)调出的“打印”对话框是非模态的(我们不太清楚microsoft的设计意图,我认为“打印”对话框应该是模态的),显示模态窗口的方法请参加我的另一篇文章《利用wh_cbt hook将非模态对话框显示为模态对话框》