win32 application invoke the html files ----2

// 该结构在命令映射中定义一个入口,这个映射将文本串映射到命令IDs,
// 如果命令映射中有一个映射到 ID_APP_ABOUT 的入口 “about”,并且 
// HTML 有一个链接锚 <A HREF="app:about">,那么单击该链接时将执行 
// ID_APP_ABOUT 命令。为了设置这个映射,调用 CHtmlCtrl::SetCmdMap. 
// 
//
struct HTMLCMDMAP {
   LPCTSTR name;     // command name used in "app:name" HREF in 
                     // <A UINT nID;
};
 
//
// 这个类将 CHtmlView 转换为普通的能在对话框和框架中使用的控制
//
class CHtmlCtrl : public CHtmlView {
}
 
//
// 当浏览器试图导航到 "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<VARIANT> 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;
}     
下面我们一步一步来分析实现过程,首先必须获取 IHTMLDocument2 接口:
SPIHTMLDocument2 doc = GetHtmlDocument();           
SPIHTMLDocument2 与 CComQIPtr<IHTMLDocument2> 一样是一个指向 IHTMLDocument2 的ATL智能指针,(当今 Windows 编程已进入 COM 时代,作为一名编写 Windows 应用程序的开发人员,如果你使用 COM 技术,但没有用过智能指针,那么这段代码会对你有所裨益),接着,必须创建一个SAFEARRAY,以便存放作为 BSTR 数组唯一元素的 HTML 串,SAFEARRAY是一个 COM 数据结构,其作用是在不同平台之间安全地传递数组数据,ATL提供了 CComBSTR 和 CComSafeArray 两个类,为开发人员在处理 BSTRs 和安全数组时减轻了许多痛苦:
// strHTML is LPCTSTR
CComSafeArray<VARIANT> 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:<HTML><B>hello, world</B></HTML>"));
针对简单的HTML可以这么做,如果比较复杂的文档则要调用 SetHTML。本文附带的例子程序动态构造了一个包含图像、表格、链接等元素的HTML文档, 该文档列出所有顶层窗口的信息,然后将它们显示出来
你可以象下面这样添加一个链接:
<A HREF="app:about">About</A>      
然后,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文档中有一个锚点链接是
// <A HREF="app:about">,则单击该链接将调用 ID_APP_ABOUT 命令。设置命令
// 映射的方法是调用 CHtmlCtrl::SetCmdMap 函数.
//
struct HTMLCMDMAP {
   LPCTSTR name;     // 用于" <A HREF..." 中的 "app:name" 的命令名.
   UINT nID;
};


// 将 CHtmlView 转换为框架或对话框中常规控制的类.类似于CListView/CListCtrl
// 和 CTreeView/CTreeCtrl
// 
class CHtmlCtrl : public CHtmlView {
protected:
   HTMLCMDMAP* m_cmdmap;   // 命令映射
   BOOL m_bHideMenu;       // 隐藏上下文菜单

public:
   CHtmlCtrl() : m_bHideMenu(FALSE), m_cmdmap(NULL) { }
   ~CHtmlCtrl() { }

   // 获取/设置 HideContextMenu 属性
   BOOL GetHideContextMenu()         { return m_bHideMenu; }
   void SetHideContextMenu(BOOL val) { m_bHideMenu=val; }

   // 根据串创建 HTML 文档
   HRESULT SetHTML(LPCTSTR strHTML);

   // 设置命令映射
   void SetCmdMap(HTMLCMDMAP* val)   { m_cmdmap = val; }

   // 先创建一个静态控制,然后用相同的再创建一个控制
   BOOL CreateFromStatic(UINT nID, CWnd* pParent);

   // 创建控制
   BOOL Create(const RECT& rc, CWnd* pParent, UINT nID,
      DWORD dwStyle = WS_CHILD|WS_VISIBLE, 
          CCreateContext* pContext = NULL)
   {
      return CHtmlView::Create(NULL, NULL, dwStyle, rc, pParent,
          nID, pContext);
   }

   // 重写用以解释子窗口消息来禁用上下文菜单
   virtual BOOL PreTranslateMessage(MSG* pMsg);

   // 通常,CHtmlView 是在 PostNcDestroy 中将自己摧毁, 
   // 但用于窗口控制,我们不想那么做,因为控制通常是作为
   // 另一个窗口对象的成员来实现的.
   //
   virtual void PostNcDestroy() {  }

   // 重写该函数以便旁路掉对 MFC 文档/视图框架的依赖. 此处是 CHtmView 依赖框架才能生存的唯一一个地方.
   afx_msg void OnDestroy();
   afx_msg int  OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, 
      UINT msg);

   // 改写该函数用以捕获 "app:" 伪协议
   virtual void OnBeforeNavigate2( LPCTSTR lpszURL,
      DWORD nFlags,
      LPCTSTR lpszTargetFrameName,
      CByteArray& baPostedData,
      LPCTSTR lpszHeaders,
      BOOL* pbCancel );

   // 你可以重写这个虚函数用以处理 "app:" 命令.
   // 如果不涉及命令映射,则不用该写.
   virtual void OnAppCmd(LPCTSTR lpszCmd);

   DECLARE_MESSAGE_MAP();
   DECLARE_DYNAMIC(CHtmlCtrl)
};

HtmlCtrl.cpp
///
// 实现 CHtmlCtrl 类 — 窗口控制中的 Web 浏览器。重写 CHtmlView 以便摆脱 
// 框架约束,可以用于对话框或任何其它窗口
//
// 特性:
// - SetCmdMap 使你能为"app:command"链接设置命令映射.
// - SetHTML 使你能将一个串设置成HTML文档内容.

#include "StdAfx.h"
#include "HtmlCtrl.h"

// 这个宏声明的 typedef 用于 ATL 智能指针,如:SPIHTMLDocument2
#define DECLARE_SMARTPTR(ifacename) typedef CComQIPtr<ifacename> SP##ifacename;

// IHTMLDocument2 接口智能指针 
DECLARE_SMARTPTR(IHTMLDocument2)

// 这是个很有用的宏,用来检查 HRESULTs
#define HRCHECK(x) hr = x; if (!SUCCEEDED(hr)) { \
   TRACE(_T("hr=%p\n"),hr);\
   return hr;\
}

// 重写函数传递 "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<VARIANT> 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 时,如果关闭程序会遇到麻烦(我费了好大的劲才发现这个问题)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值