WEB开发网
开发学院软件开发VC 获得 Win32 窗口句柄的更好的方法 阅读

获得 Win32 窗口句柄的更好的方法

 2006-07-19 11:29:08 来源:WEB开发网   
核心提示: 现在有了文档对象以及在安全数组中的内容,接下来便可以打开文档,获得 Win32 窗口句柄的更好的方法(4),进行写入操作,关闭文档等等,你会发现当执行 OnBeforeNavigate2 时,如果关闭程序会遇到麻烦(我费了好大的劲才发现这个问题),IHTMLDocument2::write

现在有了文档对象以及在安全数组中的内容,接下来便可以打开文档,进行写入操作,关闭文档等等。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文档, 该文档列出所有顶层窗口的信息,然后将它们显示出来,如图一所示。

例子程序的参考代码如下: //////////////////////////////////////
// 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("<TR><TD>%p </TD><TD>%s</TD><TD>%s</TD></TR>\n"),
     hwnd, cname, text);
   s += temp;
  }
  return TRUE;
}
////////////////////////////////////////////////////////////////////////
// 该函数创建一个文本串,这个串就是要显示在主窗口的 HTML 文档。
CString CMainFrame::FormatWindowListHTML()
{
  // start w/top matter
  CString html = _T("<HTML><BODY STYLE=\"font-family:Verdana;\" \
  link=\"#02B7B7\" vlink=\"#02B7B7\">\n\
  <TABLE WIDTH=\"100%\">\n\
  <TR><TD VALIGN=top><A target=\"new\" HREF=\"http://www.vckbase.com\"><IMG \
  BORDER=0 ALT=\"VCKBASE Online Journal\" SRC=http://tech.ddvip.com/"res://AboutHtml3.exe/mlogo.gif\"></A></TD>\
  <TD COLSPAN=2><B>AboutHtml3 例子程序 -- 顶层可见窗口清单</B><BR>\n\
  <SMALL><A target=\"new\" \
  HREF=\"http://www.vckbase.com\"> VC知识库 </A></SMALL></TD></TR>\n\
  <TR><TD><B>窗口句柄(hwnd)</B></TD><TD><B>窗口类名</B></TD><TD WIDTH=75%><B>窗口标题</B>\
  </TD><TR>\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("</TABLE>\n\
  <P><B>[<A HREF=\"app:about\">关于</A>] \
  [<A HREF=\"app:exit\">退出</A>]</B>\n\
  </BODY>\n\
  </HTML>");
  return html;
}   
最后,我想说明一下本文例子程序中其它的一些编程技巧和诀窍,主要是针对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 时,如果关闭程序会遇到麻烦(我费了好大的劲才发现这个问题)。

最后,祝大家编程愉快!

上一页  1 2 3 4 

Tags:获得 Win 窗口

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接