获得Win32窗口句柄的更好的方法
2010-06-29 20:42:26 来源:WEB开发网最后,我想说明一下本文例子程序中其它的一些编程技巧和诀窍,主要是针对CHtmlCtrl类的功能扩展。早在“VC6中使用CHtmlView在对话框控制中显示HTML文件”(第六期)一文中,我曾经演示了如何实现“app:”伪协议来创建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;\
}
... // 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<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 时,如果关闭程序会遇到麻烦(我费了好大的劲才发现这个问题)。
最后,祝大家编程愉快!
本文配套源码
更多精彩
赞助商链接