Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2096121
  • 博文数量: 909
  • 博客积分: 4000
  • 博客等级: 上校
  • 技术积分: 12260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-06 20:50
文章分类

全部博文(909)

文章存档

2008年(909)

我的朋友

分类:

2008-05-06 21:58:19

一起学习
获得 Win32 窗口句柄的更好的方法
----动态生成并显示 HTML 文档
----再谈禁用HTML的上下文菜单...

编译/NorthTibet

原文出处:MSDN Magazine C Q&A


下载源代码

译者注:
在以前的VC知识库 Online Journal 上有三篇文章:

“VC6中使用CHtmlView在对话框控制中显示HTML文件”(第六期)
“如何禁用HTML页面的上下文菜单”(第十一期)
“Convert CHtmlView to CHtmlCtrl...”(第十七期)

这三篇文章的原文实际上都出自 MSDN Magazine 及其前身 MSJ 的“C Q&A”专栏作家 Paul DiLascia 之手。此君从1995年开始就成为 MS 在 C /MFC 方面的高级写手,Paul 在 Windows 应用开发领域的造诣颇深。直到现在仍然在为该专栏撰写技术文章,只不过其文章已不仅仅涉及 C /MFC,偶尔也写一些 C#。为了微软的 .NET 战略,Paul 可谓忠实、勤奋和敬业......
本文是以上文章所涉及内容的延伸。如果你已经对前述文章讨论的东西了然于心,那么可以直接切入本文的正题。如果你没有看过上面提到的文章,建议最好先看一下,以便了解本文内容的背景,这样对于理解本文所讨论的东西会更有帮助。

背景简介
话说在第六期的“VC6中使用CHtmlView在对话框控制中显示HTML文件”一文中,主要讨论并示范了如何改进 MFC 的 CHtmlView 类,使它能处理基于对话框的应用和各种其它类型的窗口应用,其思路是通过创建 CHtmlView 的派生类 CHtmlCtrl,使得 CHtmlView 摆脱了对文档/视图的依赖。
在第十一期的“如何禁用HTML页面的上下文菜单”一文中,主要讨论了如何通过子类化 IE 服务器窗口(Internet Explorer_Server)来禁用 CHtmlCtrl 的上下文菜单。实际上,真正显示HTML的窗口并不是浏览器(CHtmlView/CHtmlCtrl)窗口,而是一个名为“Internet Explorer_Server”的最底层的子孙窗口。这一点可以通过 Spy 来证实,为了获得该窗口的句柄(HWND),在实现过程中使用了一个函数 GetLastChild(HWND hwndParent),其定义如下:

static HWND GetLastChild(HWND hwndParent)

{

   HWND hwnd = hwndParent;

   while (TRUE) {

      HWND hwndChild = ::GetWindow(hwnd, GW_CHILD);

      if (hwndChild==NULL)

         return hwnd;

      hwnd = hwndChild;

   }

   return NULL;

}

通过这个函数返回某个父窗口下的最后一个子窗口,也就是说返回子窗口的子窗口的子窗口......直到不再有子窗口为止。可惜这个函数要获得正确的运行结果是有前提的,那就是窗口层次只能是一层,并且最终的窗口后裔是“Internet Explorer_Server”窗口。 在通常情况下,这个假设都成立。不幸的是,如果 HTML 文档中包含象 ComBoxes(组合框) 这样的控制时,这个假设就不灵了。用 Spy 不难发现情况并不象你期望的那样Internet Explorer_Server是最后的子窗口。实际上,在IE中,Edit 和 Button 控制并非人们所想象的那样是子窗口。

获得 Win32 窗口句柄的更好的方法
为了解决这个问题,本文设计了一个更加完善的类:CFindWnd,用更好的算法专门来获取 IE 窗口。CFindWnd 查找某个窗口(给定窗口名字)的第一个子窗口。 例如,它的使用方法如下:
CFindWnd ies(m_hWnd, "Internet Explorer_Server");

myHwndIE = ies.m_hWnd;
这个类的构造函数调用函数:
      FindChildClassHwnd(hwndParent, (LPARAM)this)
函数,该函数又调用:
      EnumChildWindows 和 FindWindowEx    
搜索所有后裔窗口直到找到类名匹配窗口为止。FindWindow 用来查找最顶层窗口,而搜索子窗口还得用 FindWindowEx,它是 Win32 API 函数。CFindWnd 返回第一个匹配的窗口,所以它只被用于查找你期望只有一个实例的窗口。通常在搜索特定窗口时,一般最保险的做法都是检查窗口类名。

百家争鸣
有一个读者来信指出:根本没有必要使用子类IE窗口的方法来禁用上下文菜单。完全可以在 CHtmlCtrl 内部实现,象下面这样:
BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)

{

  if (pMsg->message == WM_CONTEXTMENU)

  return TRUE; // eat it

  return CHtmlView::PreTranslateMessage(pMsg);

}      

      
这样做是可行的,因为MFC实现了非常有独创性的、强大的特性─在 CWinThread 的主消息泵中,MFC 调用 CWnd::WalkPreTranslateTree 函数。这个函数循环消息目的地窗口的所有父窗口,调用每一个父窗口的 PreTranslateMessage ,一旦截获消息发送到后裔窗口则停止循环。非常聪明!
经验证明:要使前面的代码段按照期望的结果运行,你还必须截获 WM_RBUTTONDOWN 和 WM_RBUTTONDBLCLK 消息,同时还要做必要的检查以保证目标窗口的类名是 “Internet Explorer_Server”,这样就不会意外地捕获其它子窗口的上下文菜单(除非你确实要这么做)。下面是 CHtmlCtrl::PreTranslateMessage 的最终代码:
头文件 HtmlCtrl.h



////////////////////////////////////////////////////////////////

#pragma once



////////////////////////////////////////////////////////////////

// 该结构在命令映射中定义一个入口,这个映射将文本串映射到命令IDs,

// 如果命令映射中有一个映射到 ID_APP_ABOUT 的入口 “about”,并且 

// HTML 有一个链接锚 ,那么单击该链接时将执行 

// ID_APP_ABOUT 命令。为了设置这个映射,调用 CHtmlCtrl::SetCmdMap. 

// 

//

struct HTMLCMDMAP {

   LPCTSTR name;     // command name used in "app:name" HREF in 

                     //  SP##ifacename;



// IHTMLDocument2 的智能指针 

DECLARE_SMARTPTR(IHTMLDocument2)



// 一个很有用的宏,用于检查 HRESULT

#define HRCHECK(x) hr = x; if (!SUCCEEDED(hr)) { \

   TRACE(_T("hr=%p\n"),hr);\

   return hr;\

}



... // same as earlier version



// Return TRUE if hwnd is Internet Explorer window.

inline BOOL IsIEWindow(HWND hwnd)

{

   static LPCSTR IEWNDCLASSNAME = "Internet Explorer_Server";

   char classname[32]; // always char, never TCHAR

   GetClassName(hwnd, classname, sizeof(classname));

   return strcmp(classname, IEWNDCLASSNAME)==0;

}



//////////////////

// 重写后捕获 "Internet Explorer_Server" 窗口上下文菜单消息.

//

BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)

{

   if (m_bHideMenu) {

      switch (pMsg->message) {

      case WM_CONTEXTMENU:

      case WM_RBUTTONUP:

      case WM_RBUTTONDOWN:

      case WM_RBUTTONDBLCLK:

         if (IsIEWindow(pMsg->hwnd)) {

            if (pMsg->message==WM_RBUTTONUP)

               // let parent handle context menu

               GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam, 

                  pMsg->lParam);

            return TRUE; // eat it

         }

      }

   }

   return CHtmlView::PreTranslateMessage(pMsg);

}



//////////////////

// 重写后将 "app:" 链接传递到虚函数,而不是浏览器.

//

void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,

   DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData,

   LPCTSTR lpszHeaders, BOOL* pbCancel )

{

   const char APP_PROTOCOL[] = "app:";

   int len = _tcslen(APP_PROTOCOL);

   if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) {

      OnAppCmd(lpszURL   len);          // call virtual handler fn

      *pbCancel = TRUE;                 // cancel navigation

   }

}



//////////////////

// 当浏览器试图导航到 "app:foo" 时调用该函数. 

// 默认的处理例程查找"foo"命令的命令映射,并向找到的父窗口发送

// WM_COMMAND 消息。调用 SetCmdMap 设置命令映射。如果要实现更

// 复杂的处理,只要重写这个函数即可.

//

void CHtmlCtrl::OnAppCmd(LPCTSTR lpszCmd)

{

   if (m_cmdmap) {

      for (int i=0; m_cmdmap[i].name; i  ) {

         if (_tcsicmp(lpszCmd, m_cmdmap[i].name) == 0)

            // Use PostMessage to avoid problems with exit command. (Let

            // browser finish navigation before issuing command.)

            GetParent()->PostMessage(WM_COMMAND, m_cmdmap[i].nID);

      }

   }

}



//////////////////

// 将串转换为 HTML 文档

//

HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)

{

   HRESULT hr;



   // Get document object

   SPIHTMLDocument2 doc = GetHtmlDocument();



   // Create string as one-element BSTR safe array for 

   // IHTMLDocument2::write.

   CComSafeArray sar;

   sar.Create(1,0);

   sar[0] = CComBSTR(strHTML);



   // open doc and write

   LPDISPATCH lpdRet;

   HRCHECK(doc->open(CComBSTR("text/html"),

      CComVariant(CComBSTR("_self")),

      CComVariant(CComBSTR("")),

      CComVariant((bool)1),

      &lpdRet));

   

   HRCHECK(doc->write(sar));  // write contents to doc

   HRCHECK(doc->close());     // close

   lpdRet->Release();         // release IDispatch returned



   return S_OK;

}     
和以前相比,这个类功能更强,具备了 Get/SetHideContextMenu 属性处理机制,对 WM_CONTEXTMENU 消息的处理采取了发送到父窗口,而不是过滤掉它。这样就使得你能实现自己的上下文菜单。注意 WM_CONTEXTMENU 消息的发送是在鼠标右键向上释放的时候进行的,而不是按下时处理的。具体细节请参考源代码。

动态生成并显示 HTML 文档
前面的例子程序在处理HTML文档时,都是把它作为应用程序的资源进行处理的,如果碰到需要动态产生HTML文档信息的情况,这种处理方法便无法满足需要,那么如何动态 显示生成的HTML文档呢?下面我们就来解决这个问题。
大家知道,将纯文本格式化成HTML是再简单不过的事情了,虽然用C 来实现这个过程有点单调乏味,但仍然是可以做到的。如果你曾经写过JavaScript脚本,那么肯定知道加载页面时 ,可以调用 document.write 直接将HTML写到文档中。其实在C 中做法也一样,只不过编码看起来会有些繁琐和凌乱,因为要用到 COM 接口 IHTMLDocument2 以及 BSTRs、SAFEARRAYs 等数据类型来处理字符串。所幸的是 ATL 具备了大量的类可以助我们一臂之力。
下图是本文例子程序运行时的画面,使用 HTML 文档格式动态显示顶层窗口的信息:


图一 例子程序运行画面

这个程序的代码原型来自第十一期《在线杂志》中“如何禁用HTML页面的上下文菜单” 一文的例子,它实现了一个 HTML “关于”对话框,其显示的HTML页面是从资源中加载的:
m_page.LoadFromResource(_T("about.htm"));
CHtmlView::LoadFromResource 打开 res://AboutHtml.exe/about.htm,这里“AboutHtml.exe” 是可执行程序的实际名字,“res://”是一个伪协议。为了显示顶层窗口的信息,最好的办法是动态产生 HTML 页面,而不是从资源中加载,为此我在 CHtmlCtrl类中添加了一个新函数:CHtmlCtrl::SetHTML,
////////////////////////////////////

// 通过串设置 HTML 文档内容

//

HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)

{

   HRESULT hr;



   // 获得文档对象

   SPIHTMLDocument2 doc = GetHtmlDocument();



   // 创建只有一个元素(串)的 BSTR 数组元素 

   // IHTMLDocument2::write.

   CComSafeArray sar;

   sar.Create(1,0);

   sar[0] = CComBSTR(strHTML);



   // 打开文档并写入

   LPDISPATCH lpdRet;

   HRCHECK(doc->open(CComBSTR("text/html"),

      CComVariant(CComBSTR("_self")),

      CComVariant(CComBSTR("")),

      CComVariant((bool)1),

      &lpdRet));

   

   HRCHECK(doc->write(sar));  // write contents to doc

   HRCHECK(doc->close());     // close

   lpdRet->Release();         // release IDispatch returned



   return S_OK;

}      
下面我们一步一步来分析实现过程,首先必须获取 IHTMLDocument2 接口:
SPIHTMLDocument2 doc = GetHtmlDocument();           
SPIHTMLDocument2 与 CComQIPtr 一样是一个指向 IHTMLDocument2 的ATL智能指针,(当今 Windows 编程已进入 COM 时代,作为一名编写 Windows 应用程序的开发人员,如果你使用 COM 技术,但没有用过智能指针,那么这段代码会对你有所裨益),接着,必须创建一个SAFEARRAY,以便存放作为 BSTR 数组唯一元素的 HTML 串,SAFEARRAY是一个 COM 数据结构,其作用是在不同平台之间安全地传递数组数据,ATL提供了 CComBSTR 和 CComSafeArray 两个类,为开发人员在处理 BSTRs 和安全数组时减轻了许多痛苦:
// strHTML is LPCTSTR

CComSafeArray sar;

sar.Create(1,0);

sar[0] = CComBSTR(strHTML);  
如果不借助于 CComSafeArray 和 CComBSTR,而是用下列这些 API 函数来实现相同的处理,如 SafeArrayCreateVector,SafeArrayAccessData, 和 SafeArrayUnaccessData,那么至少还得写10-20行无聊的代码。一旦你上手了智能指针,你会觉得ATL的这些东西用起来真的很爽。
现在有了文档对象以及在安全数组中的内容,接下来便可以打开文档,进行写入操作,关闭文档等等。IHTMLDocument2::write需要 VARIANTS 和 BSTRs 类型的数据,这里ATL又一次显示了它的优势:
LPDISPATCH lpdRet;

doc->open(CComBSTR("text/html"),  // MIME type

  CComVariant(CComBSTR("_self")), // open in same window

  CComVariant(CComBSTR("")),      // no features

  CComVariant((bool)1),           // replace history entry

  &lpdRet));                      // IDispatch returned

doc->write(sar); // write it

doc->close();    // close

lpdRet->Release();
CHtmlCtrl::SetHTML 非常好用。使用它时有一个技巧:当第一次创建 CHtmlCtrl 时,它没有文档(GetHtmlDocument返回NULL)。所以在调用 CHtmlCtrl::SetHTML 之前,你必须创建一个文档,最简单的方法就是打开一个空文档,就象下面这样:
m_wndView.Navigate(_T("about:blank"));

此外,如果HTML很简单,你可以用 about: 代替 CHtmlCtrl::SetHTML 来得到HTML,如下面的代码:
m_wndView.Navigate(_T("about:hello, world"));
针对简单的HTML可以这么做,如果比较复杂的文档则要调用 SetHTML。本文附带的例子程序动态构造了一个包含图像、表格、链接等元素的HTML文档, 该文档列出所有顶层窗口的信息,然后将它们显示出来,如图一所示。

例子程序的参考代码如下:
//////////////////////////////////////

// HtmlApp.cpp



class CMyApp : public CWinApp {

public:

   virtual BOOL InitInstance();

protected:

   afx_msg void OnAppAbout();

   DECLARE_MESSAGE_MAP()

} theApp;



BEGIN_MESSAGE_MAP(CMyApp, CWinApp)

   ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

END_MESSAGE_MAP()





BOOL CMyApp::InitInstance()

{

   // Create main frame window (don''t use doc/view stuff)

   CMainFrame* pMainFrame = new CMainFrame;

   if (!pMainFrame->LoadFrame(IDR_MAINFRAME))

      return FALSE;

   pMainFrame->ShowWindow(m_nCmdShow);

   pMainFrame->UpdateWindow();

   m_pMainWnd = pMainFrame;



   return TRUE;

}





//////////////////////////////////////////////////////

// “关于”对话框使用 HTML 控制显示内容.

//

class CAboutDialog : public CDialog {

protected:

   CHtmlCtrl m_page;       // HTML control

   virtual BOOL OnInitDialog(); 

public:

   CAboutDialog() : CDialog(IDD_ABOUTBOX, NULL) { }

   DECLARE_DYNAMIC(CAboutDialog)

};

IMPLEMENT_DYNAMIC(CAboutDialog, CDialog)





///////////////////////

// 初始化“关于”对话框

//

BOOL CAboutDialog::OnInitDialog()

{

   // cmd map for CHtmlCtrl handles "app:ok"

   static HTMLCMDMAP AboutCmds[] = {

      { _T("ok"), IDOK },

      { NULL, 0  },

   };

   VERIFY(CDialog::OnInitDialog());

   VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW, this)); // create HTML 

                                                        // ctrl

   m_page.SetHideContextMenu(TRUE);                     // hide context 

                                                        // menu

   m_page.SetCmdMap(AboutCmds);                         // set command 

                                                        // table

   m_page.LoadFromResource(_T("about.htm"));            // load HTML from 

                                                        // resource

   return TRUE;

}



/////////////////////

// 运行“关于”对话框



void CMyApp::OnAppAbout()

{

   static CAboutDialog dlg; // static to remember state of hyperlinks

   dlg.DoModal();           // run it

}



////////////////////////////////////////////////////////////////

// MainFrm.h

// 典型的主框架处理例程......



class CMainFrame : public CFrameWnd {

public:

   CMainFrame(){ }

   virtual ~CMainFrame() { }

protected:

   CHtmlCtrl   m_wndView;               // CHtmlCtrl 作为主窗口视图

   CStatusBar  m_wndStatusBar;          // status line

   CToolBar    m_wndToolBar;            // toolbar



   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);

   afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos);



   // helper to format main window HTML

   CString FormatWindowListHTML();



   DECLARE_DYNCREATE(CMainFrame)

   DECLARE_MESSAGE_MAP()

};



////////////////////////////////////////////////////////////////

// MainFrm.cpp



IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

   ON_WM_CREATE()

   ON_WM_CONTEXTMENU()

END_MESSAGE_MAP()



// Commmand map for app: commands in main window HTML.

HTMLCMDMAP MyHtmlCmds[] = {

   { _T("about"), ID_APP_ABOUT },

   { _T("exit"),  ID_APP_EXIT  },

   { NULL, 0  },

};



int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

   VERIFY(CFrameWnd::OnCreate(lpCreateStruct)==0);



   ...



   // create/init html control as view

   VERIFY(m_wndView.Create(CRect(), this, AFX_IDW_PANE_FIRST));

   m_wndView.SetHideContextMenu(TRUE);          // 隐藏上下文菜单

   m_wndView.SetCmdMap(MyHtmlCmds);             // 设置命令

   m_wndView.Navigate(_T("about:blank"));       // 创建文档

   m_wndView.SetHTML(FormatWindowListHTML());   // 设置获取的HTML内容串

   SetActiveView(&m_wndView);                   // 设置MFC活动视图



   return 0;

}



///////////////////////////////////////////////////////////////////

// 处理上下文菜单的命令函数,当前该函数只是显示TRACE信息,以示被调用过。

//



void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint pos)

{

   TRACE(_T("CMainFrame::OnContextMenu\n"));

}



//////////////////////////////////////////////////////////////////////

// 这个函数创建在主窗口视图中显示的 HTML。

// EnumWindows 回调该函数:如果窗口可见,则将窗口信息添加到 HTML table中。

//

static BOOL CALLBACK MyEnumWindowsProc(HWND hwnd, LPARAM lp)

{

   DWORD style = GetWindowLong(hwnd, GWL_STYLE);

   if (style & WS_VISIBLE) {

      CString& s = *(CString*)lp;

      char cname[256];

      GetClassName(hwnd, cname, sizeof(cname));

      TCHAR text[1024];

      GetWindowText(hwnd, text, sizeof(text)/sizeof(text[0]));

      CString temp;

      temp.Format(_T("%p %s%s\n"),

         hwnd, cname, text);

      s  = temp;

   }

   return TRUE;

}



////////////////////////////////////////////////////////////////////////

// 该函数创建一个文本串,这个串就是要显示在主窗口的 HTML 文档。



CString CMainFrame::FormatWindowListHTML()

{

    // start w/top matter

    CString html = _T("\n\

    \n\

    \

    \n\

    \n");

    

    // enumerate top-level windows to append their info

    EnumWindows(MyEnumWindowsProc, (LPARAM)&html);



    // append bottom matter. note commands app:about and app:exit

    html  = _T("
\"VCKBASEAboutHtml3 例子程序 -- 顶层可见窗口清单
\n\ VC知识库
窗口句柄(hwnd)窗口类名窗口标题\
\n\

[关于] \ [退出]\n\ \n\ "); return html; }

最后,我想说明一下本文例子程序中其它的一些编程技巧和诀窍,主要是针对CHtmlCtrl类的功能扩展。早在“VC6中使用CHtmlView在对话框控制中显示HTML文件”(第六期)一文中,我曾经演示了如何实现“app:”伪协议来创建HTML链接(也就是锚点)与应用程序通信。例如:你可以象下面这样添加一个链接:
About      
然后,CHtmlCtrl::OnBeforeNavigate2 会识别出“app:”伪协议并以“about”作为参数调用专门的虚函数 CHtmlCtrl::OnAppCmd 。你可以创建自己的命令并在派生类中改写 OnAppCmd 来处理自己建立的命令。使用了 CHtmlCtrl 一段时间后。我发现经常需要派生 CHtmlCtrl 类,每次都得改写这个函数,自己感觉很麻烦!为了简化这个过程,我发明了一个简单的命令映射机制,利用这种机制可以轻松将“app:command”之类的转换为通常熟知的 WM_COMMAND 命令 ID:
HTMLCMDMAP MyHtmlCmds[] = {

  { _T("about"), ID_APP_ABOUT },

  { _T("exit"),  ID_APP_EXIT  },

  { NULL, 0  },

}; 
这个映射机制的使用方法是象下面这样调用 CHtmlCtrl::SetCmdMap 函数:
m_wndHtmlCtrl.SetCmdMap(MyHtmlCmds);  
这样一来,当用户单击“app:about”链接时,CHtmlCtrl::OnAppCmd 便会搜索命令映射,找到“about”入口,然后将与ID_APP_ABOUT 对应的 WM_COMMAND 消息发送到其父窗口,这个技巧主要是仰仗MFC神奇的命令路由通道实现的,借助此通道,任何窗口都可以处理此命令。真是爽啊!本文例子程序正是用这种特性将“关于”和“退出”命令作为HTML链接直接添加到主窗口中。CHtmlCtrl类实现的细节代码如下:
////////////////////////////////////////////////////////////////

// HtmlCtrl.h

#pragma once



/////////////////////////////////////////////////////////////////////////

// 此结构定义一个命令映射入口,映射将文本串映射到命令IDs。如果你的命令映射

// 入口包含 "about" 映射到ID_APP_ABOUT,并且HTML文档中有一个锚点链接是

// ,则单击该链接将调用 ID_APP_ABOUT 命令。设置命令

// 映射的方法是调用 CHtmlCtrl::SetCmdMap 函数.

//

struct HTMLCMDMAP {

   LPCTSTR name;     // 用于"  SP##ifacename;



// IHTMLDocument2 接口智能指针 

DECLARE_SMARTPTR(IHTMLDocument2)



// 这是个很有用的宏,用来检查 HRESULTs

#define HRCHECK(x) hr = x; if (!SUCCEEDED(hr)) { \

   TRACE(_T("hr=%p\n"),hr);\

   return hr;\

}



... // same as earlier version



// 如果 hwnd 是 IE 窗口,则返回 TRUE。

inline BOOL IsIEWindow(HWND hwnd)

{

   static LPCSTR IEWNDCLASSNAME = "Internet Explorer_Server";

   char classname[32]; // 必须是 char 类型, 不能是 TCHAR

   GetClassName(hwnd, classname, sizeof(classname));

   return strcmp(classname, IEWNDCLASSNAME)==0;

}



///////////////////////////////////////////////////////////////////

// 重写函数捕获 "Internet Explorer_Server" 窗口上下文菜单消息。

//

BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg)

{

   if (m_bHideMenu) {

      switch (pMsg->message) {

      case WM_CONTEXTMENU:

      case WM_RBUTTONUP:

      case WM_RBUTTONDOWN:

      case WM_RBUTTONDBLCLK:

         if (IsIEWindow(pMsg->hwnd)) {

            if (pMsg->message==WM_RBUTTONUP)

               // 让父窗口处理上下文菜单

               GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam, 

                  pMsg->lParam);

            return TRUE; // eat it

         }

      }

   }

   return CHtmlView::PreTranslateMessage(pMsg);

}



////////////////////////////////////////////////////////////////////

// 重写函数传递 "app:" 链接到虚函数,而不是浏览器。

//

void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL,

   DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData,

   LPCTSTR lpszHeaders, BOOL* pbCancel )

{

   const char APP_PROTOCOL[] = "app:";

   int len = _tcslen(APP_PROTOCOL);

   if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) {

      OnAppCmd(lpszURL   len);          // 调用虚拟函数例程

      *pbCancel = TRUE;                 // 取消导航

   }

}



////////////////////////////////////////////////////////////////////////

// 当浏览器试图导航到 "app:foo"时调用此函数. 缺省的命令处理映射为"foo",如果

// 找到命令ID,则向父窗口发送一个 WM_COMMAND 消息,调用 SetCmdMap 设置命令 

// 映射。如果你想要作稍微复杂一些的处理,必须重写 OnAppCmd。

//

void CHtmlCtrl::OnAppCmd(LPCTSTR lpszCmd)

{

   if (m_cmdmap) {

      for (int i=0; m_cmdmap[i].name; i  ) {

         if (_tcsicmp(lpszCmd, m_cmdmap[i].name) == 0)

            // 使用 PostMessage 发送消息,避免退出命令出现的问题 (在发出命令前浏览器结束导航。)

            GetParent()->PostMessage(WM_COMMAND, m_cmdmap[i].nID);

      }

   }

}



///////////////////

// 将串转为HTML文档

//

HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML)

{

   HRESULT hr;



   // 获取文档对象

   SPIHTMLDocument2 doc = GetHtmlDocument();



   // 创建串,将它作为BSTR数组的唯一个元素,因为 IHTMLDocument2::write 使用BSTR类型

   CComSafeArray sar;

   sar.Create(1,0);

   sar[0] = CComBSTR(strHTML);



   // 打开文档进行写操作

   LPDISPATCH lpdRet;

   HRCHECK(doc->open(CComBSTR("text/html"),

      CComVariant(CComBSTR("_self")),

      CComVariant(CComBSTR("")),

      CComVariant((bool)1),

      &lpdRet));

   

   HRCHECK(doc->write(sar));  // 将内容写入文档

   HRCHECK(doc->close());     // 关闭文档

   lpdRet->Release();         // 释放 IDispatch 然后返回



   return S_OK;

}     
最后一个关键的地方是 CHtmlCtrl::OnAppCmd 必须通过 PostMessage 发送命令,而不是用 SendMessage,因为如果不这样做,你会发现当执行 OnBeforeNavigate2 时,如果关闭程序会遇到麻烦(我费了好大的劲才发现这个问题)。

最后,祝大家编程愉快!
下载本文示例代码


获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法获得 Win32 窗口句柄的更好的方法
阅读(784) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~