MFC 程序员的 WTL 教程 ( 6 ) — 宿纳 ActiveX 控件
内容
简介在这第六部分里,我将介绍 ATL 对在对话框中宿纳(hosting)ActiveX 控件的支持。由于 ActiveX 控件是 ATL 的专项,所以这儿并没有相关的 WTL 类。不过,因为 ATL 宿纳控件的方式与 MFC 迥异,所以这是我们要介绍的一个重要主题。我会介绍如何宿纳控件以及接收(sink)事件,并开发一个相比用 MFC 的 ClassWizard 写就的应用毫无功能损失的应用程序。当然,你可以在你写的 WTL 应用中使用 ATL 对控件宿纳的支持。
本文的示例工程演示了如何宿纳 IE 的 Web 浏览器控件。我选择浏览器控件是基于以下两个不错的理由:
我肯定比不上那些花了很多时间使用 IE 的 Web 浏览器控件编写定制浏览器的人们。但是,通读本文之后,你就会有足够的知识开始编写你自己的定制浏览器了! 以 AppWizard 开始创建工程WTL 的 AppWizard 可以为我们创建马上就能宿纳 ActiveX 控件的应用。下面我们要创建一个称为 IEHoster 的新工程。像上一章一样,我们要使用一个非模态对话框,只不过这次要把 Enable ActiveX Control Hosting 复选框选中,就象这样: 选中这个复选框会使得我们的主对话框从 生成的代码在这一节里,我会先介绍一些原来没有见过的由 AppWizard 生成的代码片断;下一节里,我再详细介绍 ActiveX 宿纳类。 第一个需要检视的文件是 stdafx.h,其中的包含文件有: #include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlcom.h> #include <atlhost.h> #include <atlwin.h> #include <atlctl.h> // .. other WTL headers ... atlcom.h 和 atlhost.h 相对重要。它们包括了一些 COM 相关的类(比如智能指针 接下来,再看 maindlg.h 中
class CMainDlg : public CAxDialogImpl<CMainDlg>,
public CUpdateUI<CMainDlg>,
public CMessageFilter, public CIdleHandler
最后,是
int WINAPI _tWinMain(...)
{
//...
_Module.Init(NULL, hInstance);
AtlAxWinInit();
int nRet = Run(lpstrCmdLine, nCmdShow);
_Module.Term();
return nRet;
}
由于 ATL 7 的一个改动,你必须给
_Module.Init(NULL, hInstance, &LIBID_ATLLib);
这个改动在我这儿工作的很好。 使用资源编辑器添加控件ATL 允许你象在 MFC 应用中一样使用资源编辑器向对话框上添加 ActiveX。首先,在对话框编辑器中右击,选择 Insert ActiveX control: VC 会显示一个你的系统上所安装的控件的列表。向下滚动到 Microsoft Web Browser 并点击 OK,可以将该控件插入到对话框中。查看一下新控件的属性并将其 ID 设置为 IDC_IE。对话框看起来应该象下面这样,在编辑器中控件也是可见的: 如果你现在就编译并运行这个应用,你就可以在对话框中看到 Web 浏览器控件。由于我们还没有告诉它应该导航到何处,所以它显示的是一个空白页。 在下一节里,我将介绍有关创建和宿纳 ActiveX 控件的 ATL 类,然后我们再看如果使用这些类来和浏览器进行通讯。 用于宿纳控件的 ATL 类在对话框中宿纳一个 ActiveX 控件的时候,会有两个类协同工作: CAxDialogImpl第一个要介绍的就是
CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}", WS_TABSTOP,7,7,116,85 第一个参数是窗口标题(一个空串),第二个是控件 ID,第三个是窗口类名。 CONTROL "{8856F961-340A-11D0-A96B-00C04FD705A2}", IDC_IE, "AtlAxWin", WS_TABSTOP, 7, 7, 116, 85 其结果是一个 一旦 AtlAxWin 和 CAxWindow正如上述指出的,
因为 ActiveX 控件是 调用控件的方法现在,我们的对话框里就有一个 Web 浏览器了,我们可以使用它的 COM 接口来和它交互。我们要做的第一件事情是使用它的 CAxWindow wndIE = GetDlgItem(IDC_IE); 接下来,我们声明一个
CComPtr<IWebBrowser2> pWB2;
HRESULT hr;
hr = wndIE.QueryControl ( &pWB2 );
if ( pWB2 )
{
CComVariant v; // empty variant
pWB2->Navigate ( CComBSTR("http://www.codeproject.com/"),
&v, &v, &v, &v );
}
接收控件激发的事件从 Web 浏览器获取一个接口是相当简单的,而且这还可以允许我们从一个方向 – 即向控件进行通讯,还有很多的通讯,是以事件的形式从控件而来。ATL 中具有封装了连接点和事件接收的类,使得我们可以接收到由浏览器激发的事件。要使用这一支持,我们要做四件事:
VC 的 IDE 在此过程中可以提供极大的帮助 – 它会为你对 在 VC 6 里添加处理器有两种方法可以调出添加处理器的界面:
选择该命令之后,VC 会显示一个对话框,其中有一个题为 class or object to handle 的控件列表。在列表中选中 IDC_IE,则 VC 会把 WebBrowser 控件可以激发的事件填充到 New Windows message/events 列表中。 因为我们要为 DownloadBegin 事件添加处理器,所以要选中该事件并点击 Add and Edit 按钮。VC 就会提示你要求给出方法名: 在你第一次添加事件处理器时,VC 会对
#import "C:\WINNT\System32\shdocvw.dll"
class CMainDlg : public CAxDialogImpl<CMainDlg>,
public CUpdateUI<CMainDlg>,
public CMessageFilter, public CIdleHandler,
public IDispEventImpl<IDC_IE, CMainDlg>
{
// ...
public:
BEGIN_SINK_MAP(CMainDlg)
SINK_ENTRY(IDC_IE, 0x6a, OnDownloadBegin)
END_SINK_MAP()
void __stdcall OnDownloadBegin()
{
// TODO: Add Code for event handler.
}
};
继承列表中现在已经有了 接收映射由 最后面是一个新方法, 在 VC 7 里添加处理器也有两种方法可以添加事件处理器。你可以在对话框编辑器中的 ActiveX 控件上右击,并在菜单上选择 Add Event Handler。你可以在显示出的对话框里选择事件名并设置处理器的名字。 点击 Add and Edit 按钮将添加该处理器,对 另一个方法是查看 你可以点击事件名边上的箭头,选择菜单上的 <Add> [MethodName] 来添加处理器。你还可以稍后修改处理器的名字,当然还是在属性页里改。 VC 7 对 事件的知会最后一步是知会到控件, VC 6 中的知会VC 6 的 ATL 里有一个全局函数 要使用此函数,就要为
BOOL CMainDlg::OnInitDialog(...)
{
// Begin sinking events
AtlAdviseSinkMap ( this, true );
}
void CMainDlg::OnDestroy()
{
// Stop sinking events
AtlAdviseSinkMap ( this, false );
}
VC 7 中的知会在 VC 7 里, 相对于 VC 6,最大的不同在于
BEGIN_MSG_MAP(CMainDlg)
CHAIN_MSG_MAP(CAxDialogImpl<CMainDlg>)
// rest of the message map...
END_MSG_MAP()
示例工程概述我们已经知道了事件接收是怎么工作的,现在我们来看看整个 IEHost 工程。正像我们所讨论的,它宿纳了 Web 浏览器控件,并处理了六个事件。它还显示了一个事件的列表,这样你就可以知道定制浏览器是怎样使用这些事件来在 UI 上提供进度的。应用处理的事件有:
应用里还有四个控制浏览器的按钮:后退、前进、停止以及重新加载。这些按钮会调用到相应的 事件以及伴随事件的数据都被记录到了列表控件里,所以事件一激发你就能看到。你可以关闭任一事件的日志,这样你就可以只观测其中的一两个。为了演示一些实质性的事件处理,在
void __stdcall CMainDlg::OnBeforeNavigate2 (
IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,
VARIANT* TargetFrameName, VARIANT* PostData,
VARIANT* Headers, VARIANT_BOOL* Cancel )
{
CString sURL = URL->bstrVal;
// You can set *Cancel to VARIANT_TRUE to stop the
// navigation from happening. For example, to stop
// navigates to evil tracking companies like doubleclick.net:
if ( sURL.Find ( _T("doubleclick.net") ) > 0 )
*Cancel = VARIANT_TRUE;
}
下面是我们的应用在浏览论坛时的样子: IEHost 还演示了另外好几个在前文中介绍过的 WTL 特性: 运行时创建 ActiveX 控件在运行时而不是在资源编辑器中创建 ActiveX 控件也是可以的。About 对话框演示了这一技术。对话框资源包含了一个占位用的分组框,表明了浏览器控件该在什么位置: 在
LRESULT CAboutDlg::OnInitDialog(...)
{
CWindow wndPlaceholder = GetDlgItem ( IDC_IE_PLACEHOLDER );
CRect rc;
CAxWindow wndIE;
// Get the rect of the placeholder group box, then destroy
// that window because we don't need it anymore.
wndPlaceholder.GetWindowRect ( rc );
ScreenToClient ( rc );
wndPlaceholder.DestroyWindow();
// Create the AX host window.
wndIE.Create ( *this, rc, _T(""),
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN );
接下来,我们使用
CComPtr<IUnknown> punkCtrl;
CComQIPtr<IWebBrowser2> pWB2;
CComVariant v; // empty VARIANT
// Create the browser control using its GUID.
wndIE.CreateControlEx ( L"{8856F961-340A-11D0-A96B-00C04FD705A2}",
NULL, NULL, &punkCtrl );
// Get an IWebBrowser2 interface on the control and navigate to a page.
pWB2 = punkCtrl;
pWB2->Navigate ( CComBSTR("about:mozilla"), &v, &v, &v, &v );
}
对于具有 ProgID 的 ActiveX 控件,你还可以将其 ProgID 传递给
// Use the control's ProgID: Shell.Explorer:
wndIE.CreateControlEx ( L"Shell.Explorer", NULL,
NULL, &punkCtrl );
wndIE.CreateControl ( IDR_ABOUTPAGE );
下面是成果: 示例工程中包括了上述三种技术的所有代码,查点 键盘处理最后,却也非常重要的一个细节是键盘消息。ActiveX 控件的键盘处理相当复杂,因为宿主和控件必须一起合作来保证控件能够看到它感兴趣的消息。例如,浏览器控件可以让你使用 TAB 键在链接之间移动。MFC 自己会处理所有的这些,所以你可能从未意识到要使键盘支持能够正常工作所需的工作量。 不幸的是,AppWizard 不会为基于对话框的应用生成键盘处理的代码。不过,如果你创建一个 SDI 应用并使用窗体视图(form view)作为视图窗口,那么你就可以在 如果拥有焦点的窗口不认识 示例工程中包含了 下一步在下一篇文章里,我们要返回到框架窗口,介绍关于使用分割条窗口的话题。 修订历史2003 年 5 月 20 日:首次发布 |
| 链接:上一部分;下一部分 |

近期评论