📄 wtl for mfc programmers, part vi - hosting activex controls - wtl.htm
字号:
<SPAN class=cpp-comment>// ... log the URL, or whatever you'd like ...</SPAN>
}</font></PRE>
<P>我打赌你现在是越来越喜欢ClassWizard了,因为当你向MFC的对话框插入一个ActiveX控件时ClassWizard自动为你完成了所有工作。</P>
<p>将CMainDlg转换成对象需要注意几件事情,首先必须修改全局函数Run(),现在CMainDlg是个COM对象,我们必须使用CComObject创建CMainDlg:</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">int</font></SPAN><font color="#0033FF"> Run(LPTSTR <SPAN class=cpp-comment>/*lpstrCmdLine*/</SPAN> = NULL, <SPAN class=cpp-keyword>int</SPAN> nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
<B>CComObject<CMainDlg> dlgMain;
dlgMain.AddRef();</B>
<SPAN class=cpp-keyword>if</SPAN> ( dlgMain.Create(NULL) == NULL )
{
ATLTRACE(_T(<SPAN class=cpp-string>"Main dialog creation failed!\n"</SPAN>));
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}
dlgMain.ShowWindow(nCmdShow);
<SPAN class=cpp-keyword>int</SPAN> nRet = theLoop.Run();
_Module.RemoveMessageLoop();
<SPAN class=cpp-keyword>return</SPAN> nRet;
}</font></PRE>
<P>另一个可替代的方法是不使用CComObject,而使用CComObjectStack类,并删除dlgMain.AddRef()这一行代码,CComObjectStack对IUnknown的三个方法的实现有些微不足道(它们只是简单的从函数返回),因为它们不是必需的--这样的COM对象可以忽略对引用的计数,因为它们仅仅是创建在栈中的临时对象。</P>
<p>当然这并不是完美的解决方案,CComObjectStack用于短命的临时对象,不幸的是只要调用它的任何一个IUnknown方法都会引发断言错误。因为CMainDlg对象在开始监听事件时会调用AddRef,所以CComObjectStack不适用于这种情况。</p>
<p>解决这个问题要么坚持使用CComObject,要么从CComObjectStack派生一个CComObjectStack2类,允许对IUnknow方法调用。CComObject的那个不必要的引用计数并无大碍--人们不会注意到它的发生--但是如果你必须节省那个CPU时钟周期的话,你可以使用本章的例子工程代码中的CComObjectStack2类。</p>
<H2><A name=sampleoverview></A><font color="#FFFF66">回顾例子工程</font> </H2>
<P>现在我们已经看到事件响应如何工作了,再来看看完整的IEHoster工程,它包容了一个浏览器控件并响应了6个事件,它还显示了一个事件列表,你会对浏览器如何使用它们提供带进度条的界面有个感性的认识,程序处理了以下几个事件:</P>
<UL>
<LI>BeforeNavigate2和NavigateComplete2:这些事件让程序可以控制URL的导航,如果你响应了BeforeNavigate2事件,你可以在事件的处理函数中取消导航。
<LI>DownloadBegin和DownloadComplete:程序使用这些事件控制“wait”消息,这表示浏览器正在工作。一个更优美的程序会像IE一样在此期间使用一段动画。
<LI>CommandStateChange:这个事件告诉程序向前和向后导航命令何时可用,应用程序将相应的按钮变为可用或不可用。
<LI>StatusTextChange:这个事件会在几种情况下触发,例如鼠标移到一个超链接上。这个事件发送一个字符串,应用程序响应这个事件,将这个字符串显示在浏览器窗口下的静态控件上。<br>
</LI>
</UL>
<P>程序有四个按钮控制浏览器工作:向后,向前,停止和刷新,它们分别调用IWebBrowser2相应的方法。</P>
<p>事件和伴随事件发送的数据都被记录在列表控件中,你可以看到事件的触发,你还可以关闭一些事件记录而仅仅观察其中的一辆个事件。为了演示事件处理的重要作用,我们在BeforeNavigate2事件处理函数中检查URL,如果发现“doubleclick.net”就取消导航。广告和弹出窗口过滤器等一些IE的插件使用的就是这个方法而不是HTTP代理,下面就是做这些检查的代码。</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">void</font></SPAN><font color="#0033FF"> __stdcall CMainDlg::OnBeforeNavigate2 (
IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,
VARIANT* TargetFrameName, VARIANT* PostData,
VARIANT* Headers, VARIANT_BOOL* Cancel )
{
USES_CONVERSION;
CString sURL;
sURL = URL->bstrVal;
<SPAN class=cpp-comment>// You can set *Cancel to VARIANT_TRUE to stop the </SPAN>
<SPAN class=cpp-comment>// navigation from happening. For example, to stop </SPAN>
<SPAN class=cpp-comment>// navigates to evil tracking companies like doubleclick.net:</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( sURL.Find ( _T(<SPAN class=cpp-string>"doubleclick.net"</SPAN>) ) > <SPAN class=cpp-literal>0</SPAN> )
*Cancel = VARIANT_TRUE;
}</font></PRE>
<P>下面就是我们的程序工作起来的样子:</P>
<P><IMG height=479 alt=" [Sample app - 13K] "
src="images/sampleapp6.png"
width=438 align=bottom border=0></P>
<P>IEHoster还使用了前几章介绍过得类:CBitmapButton(用于浏览器控制按钮),CListViewCtrl(用于事件记录),DDX (跟踪checkbox的状态)和CDialogResize.</P>
<H2><A name=runtimecreating></A><font color="#FFFF66">运行时创建ActiveX控件</font></H2>
<P>出了使用资源编辑器,还可以在运行其间动态创建ActiveX控件。About对话框演示了这种技术。对话框编辑器预先放置了一个group box用于浏览器控件的定位:</P>
<P><IMG height=290 alt=" [About box in editor - 5K] "
src="images/aboutph6.png"
width=322 align=bottom border=0></P>
<P>在OnInitDialog()函数中我们使用 CAxWindow创建了一个新AtlAxWin,它定位于我们预先放置好的group box的位置上(这个group
box随后被销毁):<br>
</P>
<PRE><font color="#0033FF">LRESULT CAboutDlg::OnInitDialog(...)
{
CWindow wndPlaceholder = GetDlgItem ( IDC_IE_PLACEHOLDER );
CRect rc;
CAxWindow wndIE;
<SPAN class=cpp-comment>// Get the rect of the placeholder group box, then destroy </SPAN>
<SPAN class=cpp-comment>// that window because we don't need it anymore.</SPAN>
wndPlaceholder.GetWindowRect ( rc );
ScreenToClient ( rc );
wndPlaceholder.DestroyWindow();
<SPAN class=cpp-comment>// Create the AX host window.</SPAN>
wndIE.Create ( *<SPAN class=cpp-keyword>this</SPAN>, rc, _T(<SPAN class=cpp-string>""</SPAN>),
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN );</font></PRE>
<P>接下来我们用CAxWindow方法创建一个ActiveX控件,有两个方法可以选择:CreateControl()和CreateControlEx()。CreateControlEx()用一个额外的参数返回接口指针,这样就不需要再调用QueryControl()函数。我们感兴趣的两个参数是第一个和第四个参数,第一个参数是字符串形式的浏览器控件的GUID,第四个参数是一个IUnknown*类型的指针,这个指针指向ActiveX控件的IUnknown接口。创建控件后就可以查询IWebBrowser2接口,然后就可以像前面一样控制它导航到某个URL。</P>
<PRE><font color="#0033FF">CComPtr<IUnknown> punkCtrl;
CComQIPtr<IWebBrowser2> pWB2;
CComVariant v;
<SPAN class=cpp-comment>// Create the browser control using its GUID.</SPAN>
wndIE.CreateControlEx ( L<SPAN class=cpp-string>"{8856F961-340A-11D0-A96B-00C04FD705A2}"</SPAN>,
NULL, NULL, &punkCtrl );
<SPAN class=cpp-comment>// Get an IWebBrowser2 interface on the control and navigate to a page.</SPAN>
pWB2 = punkCtrl;
pWB2->Navigate ( CComBSTR(<SPAN class=cpp-string>"about:mozilla"</SPAN>), &v, &v, &v, &v );
}</font></PRE>
<P>对于有ProgID的ActiveX控件可以传递ProgID给CreateControlEx(),代替GUID。例如,我们可以这样创建浏览器控件:</P>
<PRE> <font color="#0033FF"><SPAN class=cpp-comment>// 使用控件的ProgID: 创建Shell.Explorer:</SPAN>
wndIE.CreateControlEx ( L<SPAN class=cpp-string>"Shell.Explorer"</SPAN>, NULL,
NULL, &punkCtrl );</font></PRE>
<P>CreateControl()和CreateControlEx()还有一些重载函数用于一些使用浏览器的特殊情况,如果你的应用程序使用WEb页面作为HTML资源,你可以将资源ID作为第一个参数,ATL会创建浏览器控件并导航到这个资源。IEHoster包含一个ID为IDR_ABOUTPAGE的WEB页面资源,我们在About对话框中使用这些代码显示这个页面:</P>
<PRE> <font color="#0033FF"> wndIE.CreateControl ( IDR_ABOUTPAGE );</font></PRE>
<P>这是显示结果:</P>
<P><IMG height=254 alt=" [About box browser ctrl - 6K] "
src="images/aboutbox6.png"
width=287 align=bottom border=0></P>
<P>例子代码对上面提到的三个方法都用到了,你可以查看CAboutDlg::OnInitDialog()中的注释和未注释的代码,看看它们分别是如何工作的。</P>
<H2><A name=keyboard></A><font color="#FFFF66">键盘事件处理</font></H2>
<P>最后一个但是非常重要的细节是键盘消息。ActiveX控件的键盘处理非常复杂,因为控件和它的宿主程序必须协同工作以确保控件能够看到它感兴趣的消息。例如,浏览器控件允许你使用TAB键在链接之间切换。MFC自己处理了所有工作,所以你永远不会意识到让键盘完美并正确的工作需要多么大的工作量。</P>
<p>不幸的是向导没有为基于对话框的程序生成键盘处理代码,当然,如果你使用Form View作为视图类的SDI程序,你会看到必要的代码已经被添加到PreTranslateMessage()中。当程序从消息队列中得到鼠标或键盘消息时,就使用ATL的WM_FORWARDMSG消息将此消息传递给当前拥有焦点的控件。它们通常不作什么事情,但是如果是ActiveX控件,WM_FORWARDMSG消息最终被送到包容这个控件的AtlAxWin,AtlAxWin识别WM_FORWARDMSG消息并采取必要的措施看看是否控件需要亲自处理这个消息。</p>
<p>如果拥有焦点的窗口没有识别WM_FORWARDMSG消息,PreTranslateMessage()就会接着调用IsDialogMessage()函数,使得像TAB这样的标准对话框的导航键能正常工作。</p>
<p>例子工程的PreTranslateMessage()函数中含有这些必需的代码,由于PreTranslateMessage()只在无模式对话框中有效,所以如果你想在基于对话框的应用程序中正确使用键盘就必须使用无模式对话框。
<H2><A name=upnext></A><font color="#FFFF66">继续</font></H2>
<P>在下一章,我们将回到框架窗口并介绍如何使用分隔窗口。</P>
<H2><A name=revisionhistory></A><font color="#FFFF66">修改记录</font></H2>
<P>May 20, 2003: 文章第一次发布。
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -