?? wtl for mfc programmers, part vi.mht
字號:
</B>};</PRE>
<P><CODE>CComObjectRootEx</CODE> and <CODE>CComCoClass</CODE> =
together=20
make <CODE>CMainDlg</CODE> a COM object. The template parameters =
to=20
<CODE>IDispEventSimpleImpl</CODE> are a sink ID, the name of our =
class,=20
and the IID of the connection point interface. The ID can be any =
positive=20
<CODE><SPAN class=3Dcpp-keyword>unsigned</SPAN> <SPAN=20
class=3Dcpp-keyword>int</SPAN></CODE>. The connection point =
interface is=20
<CODE>DIID_DWebBrowserEvents2</CODE>, which you can find in the =
docs on=20
the browser control, or by looking in exdisp.h.</P>
<H3><A name=3Dwritesinkmap></A>Writing the sink map</H3>
<P>The next step is to add an event sink map to =
<CODE>CMainDlg</CODE>.=20
This map lists which events we are interested in, and the names of =
our=20
event handlers. The first event we'll look at is=20
<CODE>DownloadBegin</CODE>. This event is fired when the browser =
begins=20
downloading a page, and in response we can show a "please wait" =
message so=20
the user knows that the browser is busy. Looking up=20
<CODE>DWebBrowserEvents2::DownloadBegin</CODE> in MSDN, we find =
that the=20
prototype for the event is</P><PRE> <SPAN =
class=3Dcpp-keyword>void</SPAN> DownloadBegin();</PRE>
<P>meaning the event sends no parameters and expects no return =
value. To=20
convey this prototype to the sink map, we write an=20
<CODE>_ATL_FUNC_INFO</CODE> struct that contains the return value, =
the=20
number of parameters, and their types. Since events are based on=20
<CODE>IDispatch</CODE>, all of the types involved are ones that =
can be=20
contained in a <CODE>VARIANT</CODE>. The list of types is rather =
long, but=20
here are the most common ones:</P>
<BLOCKQUOTE>
<P><CODE>VT_EMPTY</CODE>: <CODE><SPAN=20
class=3Dcpp-keyword>void</SPAN></CODE><BR><CODE>VT_BSTR</CODE>: =
String in=20
<CODE>BSTR</CODE> format.<BR><CODE>VT_I4</CODE>: 4-byte signed =
integer,=20
used for a <CODE><SPAN class=3Dcpp-keyword>long</SPAN></CODE>=20
parameter<BR><CODE>VT_DISPATCH</CODE>:=20
<CODE>IDispatch*</CODE><BR><CODE>VT_VARIANT</CODE>:=20
<CODE>VARIANT</CODE><BR><CODE>VT_BOOL</CODE>: =
<CODE>VARIANT_BOOL</CODE>=20
(possible values are <CODE>VARIANT_TRUE</CODE> and=20
<CODE>VARIANT_FALSE</CODE>)</P></BLOCKQUOTE>
<P>In addition, the flag <CODE>VT_BYREF</CODE> can be added to a =
type to=20
turn it into a pointer. For example, =
<CODE>VT_VARIANT|VT_BYREF</CODE>=20
corresponds to a type of <CODE>VARIANT*</CODE>. Here is the =
definition of=20
<CODE>_ATL_FUNC_INFO</CODE>:</P><PRE><SPAN =
class=3Dcpp-preprocessor>#define _ATL_MAX_VARTYPES 8</SPAN>
=20
<SPAN class=3Dcpp-keyword>struct</SPAN> _ATL_FUNC_INFO
{
CALLCONV cc;
VARTYPE vtReturn;
SHORT nParams;
VARTYPE pVarTypes[_ATL_MAX_VARTYPES];
};</PRE>
<P>The members are:</P>
<DL>
<DT><CODE>cc</CODE>=20
<DD>The calling convention of our handler. This must always be=20
<CODE>CC_STDCALL</CODE>, indicating the <CODE>__stdcall</CODE>=20
convention.=20
<DT><CODE>vtReturn</CODE>=20
<DD>The type of the handler's return value=20
<DT><CODE>nParams</CODE>=20
<DD>The number of parameters that the handler takes=20
<DT><CODE>pVarTypes</CODE>=20
<DD>The types of the parameters, in left-to-right order. =
</DD></DL>
<P>Knowing all this, we can write an <CODE>_ATL_FUNC_INFO</CODE> =
struct=20
for our <CODE>DownloadBegin</CODE> handler:</P><PRE>_ATL_FUNC_INFO =
DownloadInfo =3D { CC_STDCALL, VT_EMPTY, <SPAN =
class=3Dcpp-literal>0</SPAN> };</PRE>
<P>Now, on to the sink map. We put one =
<CODE>SINK_ENTRY_INFO</CODE> macro=20
in the map for each event we want to handle. Here is the macro for =
our=20
<CODE>DownloadBegin</CODE> handler:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMainDlg : <SPAN =
class=3Dcpp-keyword>public</SPAN> ...
{
...
<B> BEGIN_SINK_MAP(CMainDlg)
SINK_ENTRY_INFO(<SPAN class=3Dcpp-literal>37</SPAN>, =
DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN,
OnDownloadBegin, &DownloadInfo)
END_SINK_MAP()
</B>};</PRE>
<P>The macro parameters are the sink ID (37, the same number that =
we used=20
when adding <CODE>IDispEventSimpleImpl</CODE> to the inheritance =
list),=20
IID of the event interface, dispatch ID of the event (you can find =
these=20
in MSDN or the exdispid.h header file), the name of our handler, =
and a=20
pointer to the <CODE>_ATL_FUNC_INFO</CODE> struct that describes =
the=20
handler.</P>
<H3><A name=3Dwritehandler></A>Writing event handlers</H3>
<P>And now, at long last (whew!), we can write our event =
handler:</P><PRE><SPAN class=3Dcpp-keyword>void</SPAN> __stdcall =
CMainDlg::OnDownloadBegin()
{
<SPAN class=3Dcpp-comment>// show "Please wait" here...</SPAN>
}</PRE>
<P>Now let's look at a more complex event, like=20
<CODE>BeforeNavigate2</CODE>. The prototype for this event =
is:</P><PRE><SPAN class=3Dcpp-keyword>void</SPAN> BeforeNavigate2 (=20
IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,
VARIANT* TargetFrameName, VARIANT* PostData,
VARIANT* Headers, VARIANT_BOOL* Cancel );</PRE>
<P>This method has 7 parameters, and for the =
<CODE>VARIANT</CODE>s, you=20
should consult MSDN to see what type of data is actually being =
passed. The=20
one we're interested in is <CODE>URL</CODE>, which is a string in=20
<CODE>BSTR</CODE> format.</P>
<P>The <CODE>_ATL_FUNC_INFO</CODE> description of our=20
<CODE>BeforeNavigate2</CODE> handler is:</P><PRE>_ATL_FUNC_INFO =
BeforeNavigate2Info =3D
{ CC_STDCALL, VT_EMPTY, <SPAN class=3Dcpp-literal>7</SPAN>,
{ VT_DISPATCH, VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,
VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,
VT_BOOL|VT_BYREF }
};</PRE>
<P>As before, the return type is <CODE>VT_EMPTY</CODE> which means =
no=20
value is returned. The <CODE>nParams</CODE> member is 7, meaning =
there are=20
7 parameters. Following that is an array of parameter types. These =
types=20
were covered earlier, for example <CODE>VT_DISPATCH</CODE> =
corresponds to=20
<CODE>IDispatch*</CODE>.</P>
<P>The sink map entry is pretty similar to the previous =
one:</P><PRE> BEGIN_SINK_MAP(CMainDlg)
SINK_ENTRY_INFO(<SPAN class=3Dcpp-literal>37</SPAN>, =
DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN,
OnDownloadBegin, &DownloadInfo)
<B>SINK_ENTRY_INFO(<SPAN class=3Dcpp-literal>37</SPAN>, =
DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2,
OnBeforeNavigate2, &BeforeNavigate2Info)</B>
END_SINK_MAP()</PRE>
<P>The handler looks like:</P><PRE><SPAN =
class=3Dcpp-keyword>void</SPAN> __stdcall CMainDlg::OnBeforeNavigate2 (
IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,=20
VARIANT* TargetFrameName, VARIANT* PostData,=20
VARIANT* Headers, VARIANT_BOOL* Cancel )
{
CString sURL =3D URL->bstrVal;
=20
<SPAN class=3Dcpp-comment>// ... log the URL, or whatever you'd like =
...</SPAN>
}</PRE>
<P>I bet you appreciate ClassWizard more now! ClassWizard does all =
this=20
work for you automatically when you insert an ActiveX control into =
an MFC=20
dialog.</P>
<P>A couple of notes on the topic of making <CODE>CMainDlg</CODE> =
a COM=20
object. First, the global <CODE>Run()</CODE> function must be =
changed. Now=20
that <CODE>CMainDlg</CODE> is a COM object, we have to use=20
<CODE>CComObject</CODE> to create a =
<CODE>CMainDlg</CODE>:</P><PRE><SPAN class=3Dcpp-keyword>int</SPAN> =
Run(LPTSTR <SPAN class=3Dcpp-comment>/*lpstrCmdLine*/</SPAN> =3D NULL, =
<SPAN class=3Dcpp-keyword>int</SPAN> nCmdShow =3D SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
=20
<B>CComObject<CMainDlg> dlgMain;
=20
dlgMain.AddRef();</B>
=20
<SPAN class=3Dcpp-keyword>if</SPAN> ( dlgMain.Create(NULL) =3D=3D =
NULL )
{
ATLTRACE(_T(<SPAN class=3Dcpp-string>"Main dialog creation =
failed!\n"</SPAN>));
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
=20
dlgMain.ShowWindow(nCmdShow);
=20
<SPAN class=3Dcpp-keyword>int</SPAN> nRet =3D theLoop.Run();
=20
_Module.RemoveMessageLoop();
<SPAN class=3Dcpp-keyword>return</SPAN> nRet;
}</PRE>
<P>An alternative would be to use the <CODE>CComObjectStack</CODE> =
class,=20
instead of <CODE>CComObject</CODE>, and remove the=20
<CODE>dlgMain.AddRef()</CODE> line. <CODE>CComObjectStack</CODE> =
has=20
trivial implementations of the three <CODE>IUnknown</CODE> methods =
(they=20
just return immediately) because they are unnecessary -- such =
a COM=20
object will remain present regardless of its reference count, just =
by the=20
nature of being created on a stack.</P>
<P>However, that isn't a perfect solution. =
<CODE>CComObjectStack</CODE> is=20
meant for short-lived temporary objects, and unfortunately it =
asserts when=20
any of the <CODE>IUnknown</CODE> methods are called. Since the=20
<CODE>CMainDlg</CODE> object will be <CODE>AddRef</CODE>'ed when =
it starts=20
listening for events, <CODE>CComObjectStack</CODE> is unusable in =
this=20
situation.</P>
<P>The solution would be to either stick with =
<CODE>CComObject</CODE>, or=20
write a <CODE>CComObjectStack2</CODE> class that inherits from=20
<CODE>CComObjectStack</CODE> and allows <CODE>IUnknown</CODE> =
calls. The=20
unnecessary reference counting that happens with =
<CODE>CComObject</CODE>=20
is miniscule -- nothing that a person would ever =
notice -- but=20
if you just <I>have</I> to save those CPU cycles, you can use the=20
<CODE>CComObjectStack2</CODE> class that's included in the sample=20
project.</P>
<H2><A name=3Dsampleoverview></A>Overview of the Sample =
Project</H2>
<P>Now that we've seen how event sinking works, let's check out =
the=20
complete IEHoster project. It hosts the web browser control, as =
we've been=20
discussing, and handles six events. It also shows a list of the =
events, so=20
you can get a feel for how custom browsers can use them to provide =
progress UI. The app handles these events:</P>
<UL>
<LI><CODE>BeforeNavigate2</CODE> and =
<CODE>NavigateComplete2</CODE>:=20
These events let the app watch navigation to URLs. You can =
cancel=20
navigation if you want in response to =
<CODE>BeforeNavigate2</CODE>.=20
<LI><CODE>DownloadBegin</CODE> and =
<CODE>DownloadComplete</CODE>: The=20
app uses these events to control the "wait" message, which =
indicates=20
that the browser is working. A more polished app could use an =
animation,=20
similar to what IE itself uses.=20
<LI><CODE>CommandStateChange</CODE>: This event tells the app =
when the=20
Back and Forward navigation commands are available. The app =
enables or=20
disables the back and forward buttons accordingly.=20
<LI><CODE>StatusTextChange</CODE>: This event is fired in =
several cases,=20
for example when the cursor moves over a hyperlink. This event =
sends a=20
string, and the app responds by showing that string in a static =
control=20
under the browser window. </LI></UL>
<P>The app also has four buttons for controlling the browser: =
back,=20
forward, stop, and reload. These call the corresponding=20
<CODE>IWebBrowser2</CODE> methods.</P>
<P>The events and the data accompanying the events are all logged =
to a=20
list control, so you can see the events as they are fired. You can =
also=20
turn off logging of any events so you can watch just one or two. =
To=20
demonstrate some non-trivial event handling, the=20
<CODE>BeforeNavigate2</CODE> handler checks the URL, and if it =
contains=20
"doubleclick.net", the navigation is cancelled. Ad and popup =
blockers that=20
run as IE plugins, instead of HTTP proxies, use this method. Here =
is the=20
code that does this check.</P><PRE><SPAN =
class=3Dcpp-keyword>void</SPAN> __stdcall CMainDlg::OnBeforeNavigate2 (
IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,=20
VARIANT* TargetFrameName, VARIANT* PostData,=20
VARIANT* Headers, VARIANT_BOOL* Cancel )
{
USES_CONVERSION;
CString sURL;
=20
sURL =3D URL->bstrVal;
=20
<SPAN class=3Dcpp-comment>// You can set *Cancel to VARIANT_TRUE to =
stop the </SPAN>
<SPAN class=3Dcpp-comment>// navigation from happening. For example, =
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -