获得Win32窗口句柄的更好的方法
2010-06-29 20:42:26 来源:WEB开发网百家争鸣
有一个读者来信指出:根本没有必要使用子类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 有一个链接锚 <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 {
protected:
HTMLCMDMAP* m_cmdmap; // command map
BOOL m_bHideMenu; // hide context menu
public:
CHtmlCtrl() : m_bHideMenu(FALSE), m_cmdmap(NULL) { }
~CHtmlCtrl() { }
// get/set HideContextMenu property
BOOL GetHideContextMenu() { return m_bHideMenu; }
void SetHideContextMenu(BOOL val) { m_bHideMenu=val; }
// Set doc contents from string
HRESULT SetHTML(LPCTSTR strHTML);
// set command map
void SetCmdMap(HTMLCMDMAP* val) { m_cmdmap = val; }
// create control in same place as static control
BOOL CreateFromStatic(UINT nID, CWnd* pParent);
// create control from scratch
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 doc/view 框架的依赖,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
////////////////////////////////////////////////////////////////
// 可用于对话框和其它窗口,不需要框架
//
// 特性:
// - 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)
// 一个很有用的宏,用于检查 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<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;
}
和以前相比,这个类功能更强,具备了 Get/SetHideContextMenu 属性处理机制,对 WM_CONTEXTMENU 消息的处理采取了发送到父窗口,而不是过滤掉它。这样就使得你能实现自己的上下文菜单。注意 WM_CONTEXTMENU 消息的发送是在鼠标右键向上释放的时候进行的,而不是按下时处理的。具体细节请参考源代码。
更多精彩
赞助商链接