﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Thinking in C++-文章分类-IE编程</title><link>http://www.cppblog.com/yishanhante/category/3735.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 22 May 2008 12:10:48 GMT</lastBuildDate><pubDate>Thu, 22 May 2008 12:10:48 GMT</pubDate><ttl>60</ttl><item><title>使用Delphi开发IE按钮扩展 【转】</title><link>http://www.cppblog.com/yishanhante/articles/19751.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 13 Mar 2007 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19751.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19751.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19751.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19751.html</trackback:ping><description><![CDATA[
		<strong>介绍</strong>
		<p>
		</p>除了可以向IE中添加自定义菜单外，我们还可以向IE的工具条上添加自定义的按钮。自定义按钮同自定义菜单COM扩展的实现几乎一样， 除了在注册时需要添加的注册表项不同。 注意：同菜单扩展一样，自定义的按钮扩展也必须<ins cite="mailto:Han%20Lei" datetime="2003-09-11T19:16">是</ins>IE5及以后的版本才支持。 
<p></p>  
<p></p><b>创建COM</b><b>组件</b><p></p>  
<p></p>下面我们要创建的IE工具条按钮要稍微复杂一些，当点击时，不再只是显示一个简单的对话框了，而是让当前浏览器浏览我的个人网站<a href="http://hubdog.csdn.net/" target="_blank"> http://hubdog.csdn.net</a>。同前一节一样， 首先创建ActiveX Library，保存为IEButton.dpr，然后再新建一个名为TIEHomeButton的 COM Object，保存向导生成的文件为CIEButton.pas。 
<p></p>  
<p></p>同样的按钮扩展也需要实现IOleCommandTarget接口，同时为了能够<a name="baidusnap1"></a><b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>IE的功能，能<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>浏览器浏览指定的网址，我们还需要实现 IObjectWithSite接口。完成的类的定义如下： 
<p></p><code><b>type</b></code><pre></pre><code><b>  </b>TIEHomeButton = <b>class</b>(TComObject, IOleCommandTarget, IObjectWithSite)</code><pre></pre><code>  <b>private</b></code><pre></pre><code><b>    </b>ShellBrowser: IShellBrowser;</code><pre></pre><code>    IE:IWebBrowser;</code><pre></pre><code>  <b>protected</b></code><pre></pre><code><b>    </b><i>//IOleCommandTarget接口定义</i></code><pre></pre><code><i>    </i><b>function </b>QueryStatus(CmdGroup: PGUID; cCmds: Cardinal;</code><pre></pre><code>      prgCmds: POleCmd; CmdText: POleCmdText): HResult; <b>stdcall</b>;</code><pre></pre><code>    <b>function </b>Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;</code><pre></pre><code>      <b>const </b>vaIn: OleVariant; <b>var </b>vaOut: OleVariant): HResult; <b>stdcall</b>;</code><pre></pre><code>    <i>//IObjectWithSite</i><i>接口定义</i></code><pre></pre><code><i>    </i><b>function </b>SetSite(<b>const </b>pUnkSite: IUnknown): HResult; <b>stdcall</b>;</code><pre></pre><code>    <b>function </b><a name="baidusnap0"></a><b style="COLOR: black; BACKGROUND-COLOR: #ffff66">GetSite</b>(<b>const </b>riid: TIID; <b>out </b>site: IUnknown): HResult; <b>stdcall</b>;</code><pre></pre><code>  <b>end</b>;</code><pre></pre><code> </code><pre></pre>  
<p></p>其中IObjectWithSite接口有SetSite和<b style="COLOR: black; BACKGROUND-COLOR: #ffff66">GetSite</b>方法。其中IE会在第一次加载工具条按钮扩展时<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>SetSite，将浏览器的<br />IShellBrowser作为pUnkSite参数传递进来， 我们可以从pUnkSite参数获得IServiceProvider接口，并<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>IServiceProvider接口的QueryService获得浏览器的IWebBrower2接口，然后将IE的接口保存起来，后 面在点击按钮时，<br />我们需要<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>IWebBrowser2接口的Navigate方法浏览我的哈巴狗的小窝网站。 
<p></p>  
<p></p><code><b>function </b>TIEHomeButton.SetSite(<b>const </b>pUnkSite: IInterface): HResult;</code><pre></pre><code><b>var</b></code><pre></pre><code><b>  </b>Service:IServiceProvider;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  </b>ShellBrowser := pUnkSite <b>as </b>IShellBrowser;</code><pre></pre><code>  Service:=ShellBrowser <b>as </b>IServiceProvider;</code><pre></pre><code>  Service.QueryService(IWebBrowserApp,IWebBrowser2, IE);</code><pre></pre><code>  Result := S_OK;</code><pre></pre><code><b>end</b>;</code><pre></pre>  
<p></p>IE同时还会不时的<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b><b style="COLOR: black; BACKGROUND-COLOR: #ffff66">GetSite</b>方法来从我们保存的pUnkSite接口获得指定的riid的接口， 
<p></p><code><b>function </b>TIEHomeButton.<b style="COLOR: black; BACKGROUND-COLOR: #ffff66">GetSite</b>(<b>const </b>riid: TIID;</code><pre></pre><code>  <b>out </b>site: IInterface): HResult;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  if </b>Supports(ShellBrowser, riid, site) <b>then</b></code><pre></pre><code><b>    </b>Result := S_OK</code><pre></pre><code>  <b>else</b></code><pre></pre><code><b>    </b>Result := E_NOTIMPL;</code><pre></pre><code><b>end</b>;</code><pre></pre>  
<p></p>如果pUnkSite指针支持该接口，则返回S_OK，否则返回E_<font face="新宋体"><code> NOTIMPL</code><code>表示不支持该接口。</code></font><p></p><font face="新宋体"><code>最后，我们需要在</code><code>IOleCommandTarget</code><code>接口中实现</code><code>Exec</code><code>方法来执行浏览网站的功能，</code></font>IWebBrowser2接口的Navigate方法可以多个参数， 这里我们只需要指定要浏览的Url就可以了，其它参数都设置为空（使用EmptyParam预定义值）。 
<p></p><code><b>function </b>TIEHomeButton.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD;</code><pre></pre><code>  <b>const </b>vaIn: OleVariant; <b>var </b>vaOut: OleVariant): HResult;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  </b>Result := S_OK;</code><pre></pre><code><i>  </i>IE.Navigate(<i>''http://hubdog.csdn.net''</i>, emptyParam,emptyParam,emptyParam,emptyParam);</code><pre></pre><code><b>end</b>;</code><pre></pre>  
<p></p>注册扩展 
<p></p>  
<p></p>要想让IE能够在启动后正确显示自定义的工具条按钮扩展，需要在注册表中填写一些配置信息。 
<p></p>1.         首先要在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\项目下新建一个关键字，名为扩展的Guid的字符串形式。 
<p></p>2.         然后在新建的HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\&lt;扩展的Guid&gt;关键字下再创建一个名为CLSID 项目，设定值为{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}。 
<p></p>3.         然后在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\&lt;扩展Guid&gt;关键字下添加名为ClsidExtension的字段，这回值为按钮扩展的Guid的字符串形式。 
<p></p>4.         默认时，一个新加的扩展按钮不会马上显示在工具条上，但是我们可以在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\&lt;扩展Guid&gt;关键字下添加Default Visible字段，并设定其值为Yes，这样IE启动时会自动显示我们添加的按钮，但是要注意如果用户在添加按钮前使用了工具条 右键菜单中的自定义…命令调整过工具条按钮的显示设置，则我们的扩展按钮不会自动出现了，必须是通过自定义对话框来手工添加或者<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>重置按钮恢复默认设置来显示添加的按钮，见下图： 
<p></p><img height="184" src="http://delphi.sharpplus.com/Delphi/delphi_ie_button.files/image001.jpg" width="395" border="0" /><p></p>5.         每个按钮都要有一个显示字符串，在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer<br />\Extensions\&lt;扩展Guid&gt;关键字下添加ButtonText项目，设定其值为按钮的标题。同时按钮还需要指定两个<br />图标，一个是热点图标当鼠标停留在按钮上时显示，一个正常图标用于平时显示。图标的文件和路径需要写到<br />HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Extensions\&lt;扩展Guid&gt;关键字下的HotIcon和<br />Icon字段中，值对应于Ico文件，或者可执行文件中的图标资源，如果图标是可执行文件的资源，还需要指定<br />图标资源索引值。结果示例如下：<pre></pre>C:\PROGRA~1\AbcSoft\IEPlugin.dll,209或者 c:\IE.ico 
<p></p><b> </b><pre></pre><b>创建工具条图标</b><pre></pre> <pre></pre>工具条需要的图标文件是有一定要求的。首先是尺寸要求，每个按钮都需要提供20X20和16X16个像素的图标，其中<br />20X20的大图标用于平时显示， 16X16的图标是用于IE处于全屏幕显示状态时的界面显示，见下表示例： 
<p></p><div align="center"><table class="MsoNormalTable" cellpadding="0" border="0"><tbody><tr><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt"><b>大小（像素）</b><p></p></td><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt"><b>示例</b><p></p></td></tr><tr><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">20x20 
<p></p></td><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt"><img height="16" src="http://delphi.sharpplus.com/Delphi/delphi_ie_button.files/image002.jpg" width="17" border="0" /><p></p></td></tr><tr><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt">16x16 
<p></p></td><td style="PADDING-RIGHT: 0.75pt; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; PADDING-TOP: 0.75pt"><img height="12" src="http://delphi.sharpplus.com/Delphi/delphi_ie_button.files/image003.jpg" width="13" border="0" /><p></p></td></tr></tbody></table></div>其次有颜色上的要求，当图标处于热点状态时，推荐使用彩色图标，而当图标为正常显示状态时，推荐设定Icon字段<br />对应的图标为灰度图标。 同时微软推荐提供的图标即有256色的，也有16色的。这里为了简便起见，我只使用一个图<br />标用于热点和正常显示。 
<p></p>图标的创建可以使用专业的图标设计器来创建，不推荐使用Delphi自带的Image Editor工具，因为Image Editor只能<br />创建16X16, 32X32大小的图标， 不能创建20X20大小的图标。我一般是使用Icon Cool Editor来创建图标。 
<p></p>这就是我创建的20X20的图标<img height="16" src="http://delphi.sharpplus.com/Delphi/delphi_ie_button.files/image002.jpg" width="17" border="0" />，把它保存为Home.ico,为了能将图标文件编译进最后生成COM Server的Dll文件中，<br />编写一个资源脚本文件Home.rc: 
<p></p>1234       ICON   Home.ICO 
<p></p>注意：这里我使用1234作为图标的标识而不是使用一个更好记的文本串比如Home<ins cite="mailto:Han%20Lei" datetime="2003-09-11T19:18">，</ins>是因为IE只能使用数字标识的图标<br />作为按钮的图标。使用brcc32 编译Home.rc为Home.res。注意，在IEButton.dpr中已经有了<code>{$R *.RES}这句话表示编<br />译COM Server时会将所有的资源文件编译进生成的DLL中。</code><pre></pre><code> </code><pre></pre><code><b>注册过程</b></code><pre></pre>接下来是编写后的注册代码，都是一些对注册表的操作,注意删除时是根据Guid进行删除的，因为Guid是唯一的值： 
<p></p><code><i>//添加工具条按钮 </i></code><pre></pre><code><b>procedure </b>AddToolbarBtn(Visible: Boolean; BtnText, HotIcon,</code><pre></pre><code>  Icon, Guid: <b>string</b>);</code><pre></pre><code><b>var</b></code><pre></pre><code><b>  </b>Reg: TRegistry;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  </b>Reg := TRegistry.Create;</code><pre></pre><code>  <b>with </b>Reg <b>do</b></code><pre></pre><code><b>  try</b></code><pre></pre><code><b>    </b>RootKey := HKEY_LOCAL_MACHINE;</code><pre></pre><code>    OpenKey(<i>''\Software\Microsoft\Internet Explorer\Extensions\'' </i>+ Guid, True);</code><pre></pre><code>    <b>if </b>Visible <b>then</b></code><pre></pre><code><b>      </b>WriteString(<i>''Default Visible''</i>, <i>''Yes''</i>)</code><pre></pre><code>    <b>else</b></code><pre></pre><code><b>      </b>WriteString(<i>''Default Visible''</i>, <i>''No''</i>);</code><pre></pre><code>    WriteString(<i>''ButtonText''</i>, BtnText);</code><pre></pre><code>    WriteString(<i>''HotIcon''</i>, HotIcon);</code><pre></pre><code>    WriteString(<i>''Icon''</i>, Icon);</code><pre></pre><code>    WriteString(<i>''CLSID''</i>, <i>''{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}''</i>);</code><pre></pre><code>    WriteString(<i>''ClsidExtension''</i>, Guid);</code><pre></pre><code>    CloseKey;</code><pre></pre><code>  <b>finally</b></code><pre></pre><code><b>    </b>Free;</code><pre></pre><code>  <b>end</b>;</code><pre></pre><code><b>end</b>;</code><pre></pre><code> </code><pre></pre><code><i>//按Guid删除按钮</i></code><pre></pre><code><b>procedure </b>RemoveToolbarBtn(Guid: <b>string</b>);</code><pre></pre><code><b>var</b></code><pre></pre><code><b>  </b>Reg: TRegistry;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  </b>Reg := TRegistry.Create;</code><pre></pre><code>  <b>with </b>Reg <b>do</b></code><pre></pre><code><b>  begin</b></code><pre></pre><code><b>    </b>RootKey := HKEY_LOCAL_MACHINE;</code><pre></pre><code>    DeleteKey(<i>''\Software\Microsoft\Internet Explorer\Extensions\'' </i>+ Guid);</code><pre></pre><code>    free;</code><pre></pre><code>  <b>end</b>;</code><pre></pre><code><b>end</b>;</code><pre></pre>然后编写COM组件工厂类，<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>注册和删除注册表项的方法来实现COM组件的注册和反注册： 
<p></p><code><b>type</b></code><pre></pre><code><b>  </b>TIEHomeButtonFactory = <b>class</b>(TComObjectFactory)</code><pre></pre><code>  <b>public</b></code><pre></pre><code><b>    procedure </b>UpdateRegistry(<b>Register</b>: Boolean); <b>override</b>;</code><pre></pre><code>  <b>end</b>;</code><pre></pre><code>…</code><pre></pre><code><b>function </b>GetDllName: <b>string</b>;</code><pre></pre><code><b>var</b></code><pre></pre><code><b>  </b>Buffer: <b>array</b>[0..261] <b>of </b>Char;</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  </b>GetModuleFileName(HInstance, Buffer, SizeOf(Buffer));</code><pre></pre><code>  Result := <b>string</b>(Buffer);</code><pre></pre><code><b>end</b>;</code><pre></pre><code> </code><pre></pre><code><b>procedure </b>TIEHomeButtonFactory.UpdateRegistry(<b>Register</b>: Boolean);</code><pre></pre><code><b>begin</b></code><pre></pre><code><b>  inherited</b>;</code><pre></pre><code>  <b>if Register then</b></code><pre></pre><code><b>    </b>AddToolbarBtn(true, <i>''HomeButton''</i>, GetDllName+<i>'',1234''</i>, GetDllName+<i>'',1234''</i>, GuidToString(classid))</code><pre></pre><code>  <b>else</b></code><pre></pre><code><b>    </b>RemoveToolbarBtn(GuidToString(classid));</code><pre></pre><code><b>end</b>;</code><pre></pre><font face="新宋体"><code>上面的</code><code>GetDllName</code><code>函数会返回编译后的</code><code>Dll</code><code>名称，在</code><code>Dll</code><code>名称加上</code><code>'',1234''</code><code>表示使用</code><code>Dll</code><code>中标识为</code><code>1234</code></font><font face="新宋体"><code>的图标作为<br />按钮图标，这里为了简便起见，</code><code>HotIcon</code><code>和</code><code>Icon</code><code>使用的是同一个图标。</code></font><p></p><font face="新宋体"><code>至此，我们的按钮扩展算是大功告成了，注册扩展后，运行</code><code>IE</code><code>，点击我们的按钮，效果如下：</code></font><p></p><font face="新宋体"><img height="269" src="http://delphi.sharpplus.com/Delphi/delphi_ie_button.files/image004.jpg" width="504" border="0" /></font><pre></pre><font face="新宋体"></font>  
<p></p><b>总结</b><p></p>本节中我们主要讨论了如何可以同扩展的宿主IE浏览器进行交互，<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>浏览器的功能完成我们的需要，通过<b style="COLOR: black; BACKGROUND-COLOR: #a0ffff">调用</b>浏览器的接口我们可以实现一些更加实用更加复杂的功能，后面我们将进一步探讨。<img src ="http://www.cppblog.com/yishanhante/aggbug/19751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-13 15:19 <a href="http://www.cppblog.com/yishanhante/articles/19751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows区对象(Bands)的创建与定制</title><link>http://www.cppblog.com/yishanhante/articles/19638.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 08:07:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19638.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19638.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19638.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19638.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19638.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<b>
												<a name="一、 简介">一、 简介</a>
										</b>
										<br />Windows的区（Bands）对象有三种：既浏览栏（Explorer Bar）区对象，工具栏（Tools Bands）区对象，和桌面区对象（Desk Bands）。<br /><br /><i><b><a name="浏览栏区对象">浏览栏区对象</a></b></i><br />浏览栏区对象简称浏览栏，它是从IE4.0引入的，它是邻近浏览器窗格的一个显示区域。实际上它是IE窗口中的一个子窗口，可以用它来显示信息及与用户交互。浏览栏即可以是以垂直方式定位在浏览器窗格的左边。也可以水平方式定位在浏览器窗格下面。（如图一） </td>
						</tr>
				</tbody>
		</table>
		<p>
				<img height="390" src="http://www.vckbase.com/vckbase/vckbase11/images/112700101.GIF" width="504" />
		</p>
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>图一<br />在浏览栏中可以创建很多子菜单或选项，用户能以不同方式选择这些子菜单或选项提供的功能，打开IE或者资源管理器，从“查看”菜单中选择“浏览栏”，可以看到Windows提供了几种标准的浏览栏菜单，如“搜索（Search）”,“收藏夹（Favorites）”， 和“历史记录（History）”,以及“文件夹（All Folders）”。（如图二） </td>
						</tr>
				</tbody>
		</table>
		<p>
				<img height="320" src="http://www.vckbase.com/vckbase/vckbase11/images/112700102.GIF" width="467" />
		</p>
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>图二<br />为了创建定制的浏览栏，必须编程实现，然后注册它们。Windows在外壳（Shell）4.71中引入了区对象。它提供与普通窗口一样的功能。但因为它是以IE或外壳为容器的COM对象，所以实现起来就与普通窗口有所不同。图一中显示的就是一个简单的浏览栏例子。图中有一个垂直的浏览栏和一个水平的浏览栏。<br /><br /><b><i><a name="工具栏区对象">工具栏区对象</a></i></b><br />工具栏区对象简称工具栏，它是在IE5.0中引入用以支持单选工具栏（radio toolbar）特性的。IE工具栏实际上是一个Rebar控件，它包含了几个工具栏（toolbar）控件。通过创建工具栏，你可以将某个区对象功能添加到Rebar控件中。不论是在IE中还是在资源管理器中，区对象都是一样的，所以工具栏也是一个通用窗口。（如图三）<br /><span lang="EN-US" style="FONT-SIZE: 10.5pt; FONT-FAMILY: 'Times New Roman'; mso-bidi-font-size: 12.0pt; mso-fareast-font-family: 宋体; mso-font-kerning: 1.0pt; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"><?XML:NAMESPACE PREFIX = V /?><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></v:path><?XML:NAMESPACE PREFIX = O /?><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape id="_x0000_i1025" style="WIDTH: 214.5pt; HEIGHT: 161.25pt" type="#_x0000_t75"><v:imagedata src="file:///C:/windows/TEMP/msoclip1/01/clip_image001.png" o:title="myband3"></v:imagedata></v:shape><img height="215" src="http://www.vckbase.com/vckbase/vckbase11/images/112700103.jpg" width="286" v:shapes="_x0000_i1025" /></span><font face="宋体, MS Song"><br />图三<br /><br />用户可以从“查看”菜单中的“工具栏”子菜单中选择显示单选工具栏，也可以在工具栏区域单击鼠标右键从它的上下文菜单中选择显示单选工具栏。<br /><br /><b><i><a name="桌面区对象">桌面区对象</a></i></b><br />区对象也可以用在桌面，也就是创建桌面区对象。虽然它们的基本实现与浏览栏类似，但桌面区与IE没有关系，它不用IE作为容器。它主要用来创建桌面浮动窗口。通过在任务栏上单击右键，然后在弹出的菜单中选择“工具栏”的子菜单选项。（如图四） <br /><img height="293" src="http://www.vckbase.com/vckbase/vckbase11/images/112700104.GIF" width="343" border="0" /><br />图四<br /><br />桌面区的初始浮动位置在任务栏：（如图五<br /><img height="103" src="http://www.vckbase.com/vckbase/vckbase11/images/112700105.GIF" width="532" border="0" /><br />图五<br /><br />用户可以将桌面区拖到桌面上，这时它就成了一个普通窗口：（如图六）<br /><img height="127" src="http://www.vckbase.com/vckbase/vckbase11/images/112700106.GIF" width="480" border="0" /><br />图六<br /></font></td>
						</tr>
				</tbody>
		</table>
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<b>
												<a name="二、实现区对象">二、实现区对象</a>
												<br />
										</b>尽管可以像使用普通窗口一样使用区对象，但它们毕竟是COM对象，存在于某个容器之中。如浏览栏和工具栏位于IE之中，桌面区位于外壳之中。虽然它们的功能不同，但其基本实现非常相似。一个主要的差别是它们的注册方式不同，而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现细节可参考<a href="http://www.vckbase.com/vckbase/vckbase11/vc/nonctrls/shellpro_27/1127001.htm#sample1" target="_self">垂直浏览栏例子程序</a>。<br />区对象除了要实现 IUnknown 和 IClassFactory 两个接口之外，所有的区对象还必须实现以下这几个接口：<b></b><ul><li> IDeskBand  
</li><li> IObjectWithSite  
</li><li> IPersistStream </li></ul>另外，在注册时除了注册它们的CLSID之外，浏览栏和桌面区对象还必须进行组件类别（category）的注册。它决定了对象的类型及其容器。工具栏不需要进行种类注册。归纳起来，需要进行CATID注册的三种区对象是：<br /><table height="64" width="100%" border="1"><tbody><tr><td align="middle" width="50%" height="8"><b>区对象类型</b></td><td align="middle" width="50%" height="8"><b>组件类型</b></td></tr><tr><td align="middle" width="50%" height="1">垂直浏览栏</td><td align="middle" width="50%" height="1">CATID_InfoBand</td></tr><tr><td align="middle" width="50%" height="16">水平浏览栏</td><td align="middle" width="50%" height="16">CATID_CommBand</td></tr><tr><td align="middle" width="50%" height="16">桌面区</td><td align="middle" width="50%" height="16">CATID_DeskBand</td></tr></tbody></table><br />对于如何注册区对象的进一步讨论请参见<a href="http://www.vckbase.com/vckbase/vckbase11/vc/nonctrls/shellpro_27/1127001.htm#howtoreg" target="_self">注册部分</a>。<br />如果某个区对象接受用户输入，它还必须实现IInputObject接口。如果要往上下文菜单中添加菜单项目，还必须实现IContextMenu接口。注意：工具栏区对象不支持上下文菜单。<br />    因为区对象实现的是子窗口，所以它们还必须有窗口过程来处理Windows的消息。<br />    区对象可以通过其IOleCommandTarget接口发送命令到它的容器。为了得到这个接口的指针，必须调用容器的IInputObjectSite::QueryInterface方法<xxxxime xime="T">来</xxxxime>请求IID_IoleCommandTarget。然后用IOleCommandTarget::Exec把命令发送到容器。命令组是CGID_DeskBand。当某个区对象的IDeskBand::GetBandInfo方法被调用时，容器用dwBandID参数将一个标示符赋给这个对象。这个标示符被用于IOleCommandTarget::Exec方法调用时所用命令组中的三个命令。目前命令组共支持四个IOleCommandTarget::Exec命令IDs。这四个命令的解释如下：<br />DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最近一次调用所用的band标示符。容器将调用这个标示符所指的band对象的IDeskBand::GetBandInfo方法请求更新的信息。<br />DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最近一次调用所用的band标示符。<br />DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的值为VT_UNKNOWN类型，可以取下列值之一：<br /><table width="100%" border="1"><tbody><tr><td valign="top" align="middle" width="24%">值</td><td width="76%">描述</td></tr><tr><td valign="top" align="middle" width="24%">pUnk</td><td width="76%">这个对象IUnknown接口的指针。所有其它的桌面band将被隐藏。</td></tr><tr><td valign="top" align="middle" width="24%">0</td><td width="76%">隐藏所有桌面band。</td></tr><tr><td valign="top" align="middle" width="24%">1</td><td width="76%">显示所有桌面band。</td></tr></tbody></table>DBID_PUSHCHEVRON——目前没有实现。<br /><br /><a name="howtoreg"><b><i>注册</i></b></a><br />区对象必须作为进程内服务器（in-process）注册。其线程模型必须为“Apartment”。也就是说区对象必须以DLL的形式来实现。用来描述服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源管理器或IE “查看（View）”菜单的“浏览栏（Explorer Bar）”子菜单中。而工具栏的菜单则出现在资源管理器或IE “查看（View）”菜单的“工具栏（Toolbars）”子菜单中。桌面区出现在任务栏上下文菜单的“工具栏（Toolbars）”子菜单中。作为菜单资源，提供键盘快捷的方法与一般菜单快捷键相同。也就是将“&amp;”字符放在某个单词字母前表示这个字母显示下划线来指示快捷键。<br />通常区对象的注册条目如下:<br /><pre>HKEY_CLASSES_ROOT
...
    CLSID
	...
        {Band 对象的 CLSID GUID} = "菜单文本串"
            InProcServer32 = "DLL 路径名"
                ThreadingModel = "Apartment"</pre>工具栏区对象必须还要注册对象的CLSID。为此必须在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创建一个REG_SZ值，用工具栏区对象的CLSID GUID串命名。如：<br /><pre>HKEY_LOCAL_MACHINE
    Software
        Microsoft
            Internet Explorer
                Toolbar
                    { Band 对象的 CLSID GUID }</pre><br />除此之外，还有几个可选的注册值可以加到注册表中，本文的例子中未使用这些值。 <br /><ul><li>HKEY_CLASSES_ROOT\CLSID\{Band 对象的 CLSID GUID}\Instance\CLSID, 它应该被设置为 "{4D5C8C2A-D075-11D0-B416-00C04FB90376}". <br /></li><li>HKEY_CLASSES_ROOT\CLSID\{Band对象的CLSID GUID}\Instance\InitPropertyBag\Url 它应该被设置为要在浏览栏显示的包含HTML内容的文件位置。<br /></li><li>\HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Explorer Bars\{Band 对象的 CLSID GUID}\BarSize 它应该被设置为栏目的高和宽，它需要八个字节才能作为串放入注册表，字节之间用逗号分开。开始的四个字节一像素为单位指定大小，格式要用十六进制，从最左边字节开始。最后四个字节是保留字节，应该将它置为零。例如，垂直浏览栏的缺省宽度为291（0x123）像素，则BarSize 的值应该是"23,01,00,00,00,00,00,00"  </li></ul>如果要用浏览栏显示HTML，则前两个注册项是必须的。最后一个注册项则根据垂直的或者水平的浏览栏定义相应的缺省宽度和高度。<br />能显示HTML的浏览栏（缺省宽度为291各像素单位）注册表条目的形式如下：<br /><pre>HKEY_CLASSES_ROOT
...
    CLSID
	...
        {Band 对象的 CLSID GUID} = "菜单文本串"
            InProcServer32 = "DLL 路径名"
                ThreadingModel = "Apartment"
            Instance
                CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}"
                InitPropertyBag
                    Url = "HTML文件"
...
HKEY_CURRENT_USER
...
    Software
    ...
        Microsoft
        ...
            Internet Explorer
            ...
                Explorer Bars
                    { Band 对象的 CLSID GUID }
                        BarSize = "23,01,00,00,00,00,00,00"</pre><br />你可以通过编程的方式来处理区对象类别 CATID 的注册。创建一个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)并请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories。<br /><b><a name="sample1">三、定制浏览栏的一个简单例子</a></b><br />这个例子展示了前面所介绍过的垂直浏览栏的整个实现过程。它借助了平台SDK（Platform SDK——在msdn中可以找到）中关于band对象示范代码。其中还包括了水平浏览栏和桌面band的实现代码。详细实现细节请参见：CommBand.cpp和DeskBand.cpp。<br />创建定制浏览栏的基本过程是这样的：<br /><ol><li>实现DLL需要的函数。 
</li><li>实现必须的COM接口。 
</li><li>实现任何想要的可选接口。 
</li><li>注册对象的CLSID。 
</li><li>进行恰当的组件种类注册。 
</li><li>创建IE子窗口，调整窗口大小适合浏览栏的显示区域。 
</li><li>使用子窗口显示信息并与用户交互。 </li></ol>实际上，只要通过恰当的组件种类注册，浏览栏例子代码便既可用于浏览栏的实现，也能用于桌面band实现。更加复杂的实现将需要定制每种对象类型的显示区域和容器。但大多数的定制工作都能通过范例代码以及Windows子窗口的编程技术来完成。例如，你可以添加用户交互控制或者进行色彩丰富的图形显示处理。<br /><br /><b><i><a name="DLL函数">DLL函数</a></i></b><br />所有三种区对象被打包在一个DLL中，它输出以下的函数：<br /><ul><li>DllMain 
</li><li>DllCanUnloadNow  
</li><li>DllGetClassObject  
</li><li>DllRegisterServer  </li></ul>这些函数可以在BandObjs.cpp中找到，它们服务于所有三种区对象。前三个函数乃标准的实现，我们不再本文中讨论。类工厂也是标准实现，代码可以在ClsFact.cpp中找到<br /><br /><b><i><a name="注册定制的浏览栏">注册定制的浏览栏</a></i></b><br /><br />有了COM对象后，必须对浏览栏的CLSID进行注册。另外如果要与IE或资源管理器<br />协调运行，还必须进行的恰当的组件种类（CATID_InfoBand）注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下：<br /><pre>...
//注册浏览栏对象
if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直浏览栏例子")))
return SELFREG_E_CLASS;

//注册浏览栏的对象组件种类
if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand))
return SELFREG_E_CLASS;
...</pre>区对象的注册使用通常的COM过程，它由私有函数RegisterServer处理。<br />除了CLSID之外，这个区对象服务器还必须注册一个以上的组件种类。这实际上是垂直浏览栏和水平浏览栏实现之间的主要差别。这个过程的处理是通过创建一个组件种类管理器对象（CLSID_StdComponentCategoriesMgr），并用ICatRegister::RegisterClassImplCategories方法来注册区对象服务器。在这个例子中，组件种类注册的处理是通过将浏览栏的CLSID和CATID传递到私有函数RegisterComCat完成的：<br /><pre>BOOL RegisterComCat(CLSID clsid, CATID CatID)
{
ICatRegister   *pcr;
HRESULT        hr = S_OK ;
    
CoInitialize(NULL);

hr = CoCreateInstance(  CLSID_StdComponentCategoriesMgr, 
                        NULL, 
                        CLSCTX_INPROC_SERVER, 
                        IID_ICatRegister, 
                        (LPVOID*)&amp;pcr);
if(SUCCEEDED(hr))
   {
   hr = pcr-&gt;RegisterClassImplCategories(clsid, 1, &amp;CatID);
   pcr-&gt;Release();
   }
        
CoUninitialize();

return SUCCEEDED(hr);
}</pre></td>
						</tr>
				</tbody>
		</table>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<b>
												<i>
														<a name="必须实现的接口">必须实现的接口</a>
														<br />
												</i>
										</b>垂直浏览栏例子实现了四个必须的接口：IUnknown, IObjectWithSite, IPersistStream, 和IDeskBand，它们都在CExplorerBar类中实现。<br /><u><a name="IUnknown">IUnknown</a></u><br />构造函数，析构函数和IUnknown实现比较简单，本文在此不讨论。细节请参见源代码。<br /><i><u><a name="IObjectWithSite接口">IObjectWithSite接口</a></u></i><br />当用户选择某个浏览栏时，容器调用相应band对象的IObjectWithSite::SetSite方法。参数将被设置成这个现场（Site）的IUnknown指针。<br />通常，SetSite实现应该完成下列步骤：<br /><ol><li>释放当前所把持的任何现场指针。 
</li><li>如果传递到SetSite的指针被置为NULL，此则区对象被删除。SetSite可以返回S_OK。 
</li><li>如果传递到SetSite的指针被置为非NULL，则建立新的现场。SetSite应该做以下的事情：<br /></li></ol><ol type="i"><li>调用现场QueryInterface方法请求IOleWindow接口。 
</li><li>调用IOleWindow::GetWindow获取父窗口句柄，并存储它，以便以后使用。如果不再使用的话，就释放IOleWindow接口。 
</li><li>创建此band对象的窗口为一个子窗口，其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。 
</li><li>如果此band对象实现IInputObject，调用现场QueryInterface方法请求IInputObjectSite接口，存储这个接口的指针以备后用。 
</li><li>如果所有步骤都成功，则返回S_OK，否则返回OLE定义的错误代码以指示错误类型。 </li></ol>以下是浏览栏实现SetSite的方法。m_pSite是私有成员变量，用它来保存IInputObjectSite指针，而m_hwndParent保存父窗口句柄。<br /><pre>STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite)
{
//如果某个现场被把持，则释放它
if(m_pSite)
   {
   m_pSite-&gt;Release();
   m_pSite = NULL;
   }

//如果punkSite 不为NULL, 建立一个新的现场
if(punkSite)
   {
   //获取父窗口
   IOleWindow  *pOleWindow;

   m_hwndParent = NULL;
   
   if(SUCCEEDED(punkSite-&gt;QueryInterface(IID_IOleWindow, (LPVOID*)&amp;pOleWindow)))
      {
      pOleWindow-&gt;GetWindow(&amp;m_hwndParent);
      pOleWindow-&gt;Release();
      }

   if(!m_hwndParent)
      return E_FAIL;

   if(!RegisterAndCreateWindow())
      return E_FAIL;

   //获取柄保存IInputObjectSite指针
   if(SUCCEEDED(punkSite-&gt;QueryInterface(IID_IInputObjectSite, (LPVOID*)&amp;m_pSite)))
      {
      return S_OK;
      }  
   return E_FAIL;
   }
return S_OK;
}</pre>这个例子的GetSite只简单地用SetSite保存的现场指针实现了对现场QueryInterface方法的调用。<br /><pre>STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn)
{
*ppvReturn = NULL;

if(m_pSite)
   return m_pSite-&gt;QueryInterface(riid, ppvReturn);

return E_FAIL;
}</pre>窗口创建由私有方法RegisterAndCreateWindow负责。如果这个窗口不存在，此方法将浏览栏窗口创建成一个大小适当的子窗口，它的父窗口就是由SetSite获得的那个窗口。子窗口的句柄存储在m_hwnd变量中。<br /><pre>BOOL CExplorerBar::RegisterAndCreateWindow(void)
{
//如果这个窗口不存在，则创建它
if(!m_hWnd)
   {
   //子窗口不能没有父窗口
   if(!m_hwndParent)
      {
      return FALSE;
      }

   //如果窗口类没有注册，则必须注册
   WNDCLASS wc;
   if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &amp;wc))
      {
      ZeroMemory(&amp;wc, sizeof(wc));
      wc.style          = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
      wc.lpfnWndProc    = (WNDPROC)WndProc;
      wc.cbClsExtra     = 0;
      wc.cbWndExtra     = 0;
      wc.hInstance      = g_hInst;
      wc.hIcon          = NULL;
      wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground  = (HBRUSH)CreateSolidBrush(RGB(0, 0, 192));
      wc.lpszMenuName   = NULL;
      wc.lpszClassName  = EB_CLASS_NAME;
      
      if(!RegisterClass(&amp;wc))
         {
         //如果注册失败，下面的CreateWindow函数将失败
         }
      }

   RECT  rc;

   GetClientRect(m_hwndParent, &amp;rc);

   //创建这个窗口。WndProc 将建立m_hWnd变量
   CreateWindowEx(   0,
                     EB_CLASS_NAME,
                     NULL,
                     WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER,
                     rc.left,
                     rc.top,
                     rc.right - rc.left,
                     rc.bottom - rc.top,
                     m_hwndParent,
                     NULL,
                     g_hInst,
                     (LPVOID)this);
      
   }
return (NULL != m_hWnd);
}</pre><i><u><a name="IPersistStream接口">IPersistStream接口</a></u></i><br />IE将调用浏览栏的IPersistStream接口，以便允许这个浏览栏加载或存储持久性数据。如果没有持久性数据，这个方法仍然必须返回一个成功代码。IPersistStream接口从IPersist继承而来，所以要实现五个方法：<br />GetClassID, IsDirty, Load, Save, GetSizeMax。<br />本文的这个浏览栏例子不使用持久性数据，并且只有IPersistStream的最小实现。GetClassID返回对象的CLSID（CLSID_SampleExplorerBar），其余的方法返回S_OK, 或者S_FALSE, 或者 E_NOTIMPL。有关细节请参见IPersistStream的实现。<br /><br /><i><u><a name="IDeskBand接口">IDeskBand接口</a></u></i><br />IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindow继承而来，而IDockingWindow又从IOleWindow继承而来。<br />IOleWindow有两个方法：GetWindow 和 ContextSensitiveHelp。浏览栏例子的GetWindow实现返回浏览栏的子窗口句柄m_hwnd。因为不实现上下文敏感帮助，所以ContextSensitiveHelp返回E_NOTIMPL。<br />IDockingWindow接口有三个方法：ShowDW, CloseDW, 和 ResizeBorder。ResizeBorder不在任何区对象中使用，应该返回E_NOTIMPL。ShowDW方法根据其不同的参数值控制浏览栏窗口的显示或隐藏：<br /><pre>STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow)
{
if(m_hWnd)
   {
   if(fShow)
      {
      //显示窗口
      ShowWindow(m_hWnd, SW_SHOW);
      }
   else
      {
      //隐藏窗口
      ShowWindow(m_hWnd, SW_HIDE);
      }
   }

return S_OK;
}

CloseDW方法摧毁浏览栏窗口：
STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved)
{
ShowDW(FALSE);

if(IsWindow(m_hWnd))
   DestroyWindow(m_hWnd);

m_hWnd = NULL;
   
return S_OK;
}</pre>其余的方法，如GetBandInfo是IDeskBand专用的。IE使用它来指定浏览栏的标示符以及视图模式。IE还可能填写DESKBANDINFO结构的dwMask成员从浏览栏请求更多的信息，这个结构用第三个参数传递。GetBandInfo应该存储这个标示符和视图模式并用所请求的数据填写DESKBANDINFO结构。下面是本文浏览栏例子所实现GetBandInfo：<br /><pre>STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
{
if(pdbi)
   {
   m_dwBandID = dwBandID;
   m_dwViewMode = dwViewMode;

   if(pdbi-&gt;dwMask &amp; DBIM_MINSIZE)
      {
      pdbi-&gt;ptMinSize.x = MIN_SIZE_X;
      pdbi-&gt;ptMinSize.y = MIN_SIZE_Y;
      }
   if(pdbi-&gt;dwMask &amp; DBIM_MAXSIZE)
      {
      pdbi-&gt;ptMaxSize.x = -1;
      pdbi-&gt;ptMaxSize.y = -1;
      }
   if(pdbi-&gt;dwMask &amp; DBIM_INTEGRAL)
      {
      pdbi-&gt;ptIntegral.x = 1;
      pdbi-&gt;ptIntegral.y = 1;
      }
   if(pdbi-&gt;dwMask &amp; DBIM_ACTUAL)
      {
      pdbi-&gt;ptActual.x = 0;
      pdbi-&gt;ptActual.y = 0;
      }
   if(pdbi-&gt;dwMask &amp; DBIM_TITLE)
      {
      lstrcpyW(pdbi-&gt;wszTitle, L"浏览栏例子");
      }
   if(pdbi-&gt;dwMask &amp; DBIM_MODEFLAGS)
      {
      pdbi-&gt;dwModeFlags = DBIMF_VARIABLEHEIGHT;
      }
   if(pdbi-&gt;dwMask &amp; DBIM_BKCOLOR)
      {
      //通过移开这个标志来使用默认的背景颜色
      pdbi-&gt;dwMask &amp;= ~DBIM_BKCOLOR;
      }
   return S_OK;
   }
return E_INVALIDARG;
}</pre><b><i><a name="可选择的接口实现">可选择的接口实现</a></i></b><br />由两个接口的实现是可选择的，一个是IInputObject，另一个是 IContextMenu。本文的浏览栏例子实现了IInputObject。对于IContextMenu的实现细节请参考有关文档。<br /><br /><i><u><a name="IInputObject接口">IInputObject接口</a></u></i><br />如果某个band对象要接受用户输入。那就必须实现IInputObject接口。IE实现IInputObjectSite并用IInputObject维护用户的输入焦点。浏览栏需要实现三个方法：UIActivateIO, HasFocusIO, 和 TranslateAcceleratorIO。<br />IE调用UIActivateIO通知浏览栏它以被激活或者被置灰。当被激活时，浏览栏例子调用SetFocus来设置窗口输入焦点。<br />当要确定哪个窗口有输入焦点时，IE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点，HasFocusIO返回S_OK。否则，它返回S_FALSE。<br />TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现这个方法，所以它返回S_FALSE。<br />浏览栏例子实现IInputObjectSite的细节如下：<br /><pre>STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
{
if(fActivate)
   SetFocus(m_hWnd);

return S_OK;
}

STDMETHODIMP CExplorerBar::HasFocusIO(void)
{
if(m_bFocus)
   return S_OK;

return S_FALSE;
}

STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
{
return S_FALSE;
}</pre><b><i><a name="窗口过程">窗口过程</a></i></b><br />因为区对象的显示用的是子窗口，所以它必须实现窗口过程来处理Windows消息。浏览栏例子实现了一个最简单的版本，它的窗口过程只处理了五个消息：WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, 和 WM_KILLFOCUS。如果要实现更多的功能，很容易扩充使它处理其它的消息。<br /><pre>LRESULT CALLBACK CExplorerBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
CExplorerBar  *pThis = (CExplorerBar*)GetWindowLong(hWnd, GWL_USERDATA);

switch (uMessage)
   {
   case WM_NCCREATE:
      {
      LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
      pThis = (CExplorerBar*)(lpcs-&gt;lpCreateParams);
      SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);

      //设置窗口句柄
      pThis-&gt;m_hWnd = hWnd;
      }
      break;  
   case WM_PAINT:
      return pThis-&gt;OnPaint();  
   case WM_COMMAND:
      return pThis-&gt;OnCommand(wParam, lParam);  
   case WM_SETFOCUS:
      return pThis-&gt;OnSetFocus();
   case WM_KILLFOCUS:
      return pThis-&gt;OnKillFocus();
   }
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}</pre>这里WM_COMMAND消息处理器简单地返回零。WM_PAINT消息处理器创建文本并显示在资源管理器或IE的区对象中。<br /><pre>LRESULT CExplorerBar::OnPaint(void)
{
PAINTSTRUCT ps;
RECT        rc;

BeginPaint(m_hWnd, &amp;ps);
GetClientRect(m_hWnd, &amp;rc);
SetTextColor(ps.hdc, RGB(255, 255, 255));
SetBkMode(ps.hdc, TRANSPARENT);
DrawText(ps.hdc, TEXT("浏览栏例子"), -1, &amp;rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(m_hWnd, &amp;ps);
return 0;
}</pre>WM_SETFOCUS 和 WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeIS方法通知输入焦点现场改变：<br /><pre>LRESULT CExplorerBar::OnSetFocus(void)
{
FocusChange(TRUE);

return 0;
}

LRESULT CExplorerBar::OnKillFocus(void)
{
FocusChange(FALSE);
return 0;
}

void CExplorerBar::FocusChange(BOOL bFocus)
{
m_bFocus = bFocus;

//通知焦点已改变的输入对象现场
if(m_pSite)
   {
   m_pSite-&gt;OnFocusChangeIS((IDockingWindow*)this, bFocus);
   }
}</pre><b><a name="四、总结">四、总结</a></b><br /><br />区对象提供了灵活和强大的扩展方式，通过定制浏览栏使得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽管需要一些对COM的编程，但终究以子窗口的形式提供了一种用户界面。从而使今后的许多这种编程实现都能用类似的Windows编程技术。虽然本文所讨论的例子只提供了有限的功能，但它示范了区对象全部的特性，并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。 </td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19638.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 16:07 <a href="http://www.cppblog.com/yishanhante/articles/19638.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏</title><link>http://www.cppblog.com/yishanhante/articles/19566.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Sun, 11 Mar 2007 06:35:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19566.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19566.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19566.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19566.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19566.html</trackback:ping><description><![CDATA[
		<strong>一、引言<br /></strong>　　最近，由于工作的要求，我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料，根据 MSDN 上的说明我用 ATL 胜利完成了“资本家老板”分配的任务。<br />（并且在白天睡觉的过程中梦到了老板给我加工资啦......）<br />现在，我把 MSDN 上的原文资料，经过翻译整理并把一个 ATL 的实现奉贤给 VCKBASE 上的朋友们。
<ul><li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#二、概念">概念</a></li><li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#三、原理">原理</a><br /><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.1　基本_band_对象">基本band 对象</a><br /><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.2　必须实现的_COM_接口">必须实现的 COM 接口</a><br />    <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IPersistStream">IPersistStream</a><br />    <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IObjectWithSite">IObjectWithSite</a><br />    <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IDeskBand">IDeskBand、IDockingWindow、IOleWindow</a><br /><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.3　选择实现的_COM_接口">选择实现的 COM 接口</a><br /><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.4　Band_对象注册">Band 对象注册</a></li><li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#四、_ATL_实现">ATL 实现</a></li></ul><p><b><a name="二、概念">二、概念</a></b><br />　　在翻译的过程中，有两个词汇非常不好理解。第一个词是 Band 对象，词典中翻译为“镶边、裙子边、带子、乐队......”我的英文水平有限，实在不知道应该翻译为什么词汇更合适。于是我毅然决然地决定：在如下的论述中，依然使用 band 这个词！（什么？没听明白？我的意思就是说，我不翻译这个词了）但到底 Band 对象应该如何理解那？请看图一：<br /><br /><img height="399" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands1.jpg" width="613" border="0" /><br />图一<br /><br />　　图一中画红圈的地方，分别称作“垂直的浏览器栏”、“水平的浏览器栏”、“工具栏”和“桌面工具栏”。这些“栏”，都可以在 IE 的“查看”菜单中或鼠标右键的上下文快捷方式菜单中显示或隐藏起来。这些界面窗口的实现，其实就是实现一种 COM 接口对象，而这个对象叫 band。这个概念实在是只能意会而无法言传的，我总不能在文章中把它翻译为“总是靠在 IE 主窗口边上的对象”吧？^_^<br />　　另外，还有一个词叫 site。这个很好翻译，叫“站点”！。呵呵，我敢打包票，如果你要能理解这个翻译在计算机类文章中的含义，那就只能恭喜你了，你的智慧太高了。（都是学计算机软件的人，做人的差距咋就这么大呢？）在本篇文章中，site 可以这样理解：IE 的主框架四周，就好比是“汽车站”，那些 band 对象，就好比是“汽车”。band 汽车总是可以停靠在“汽车站”上。所以，site 就是“站点”，它也是 COM 接口的对象（IObjectWithSite、IInputObjectSite）。<br /><br /><b><a name="三、原理">三、原理</a></b><br /><br /><i><a name="3.1　基本_band_对象">3.1　基本 band 对象</a></i><br />　　Band 对象，从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一个 COM 对象，必须放在一个容器中去使用，当然使用它们就好象使用普通窗口是一样的。IE 就是一个容器，桌面 Shell 也是一个容器，它们提供不同的函数功能，但基本的实现是相似的。<br />　　Band 对象分三种类型，浏览器栏 band（Explorer bands）、工具栏 band（Tool Bands）和桌面工具栏(Desk bands)，而浏览器栏 band 又有两种表现形式：垂直和水平的。那么 IE 和 Shell 如何区分并加载这些 bands 对象呢？方法是：你要对不同的 band 对象，在注册表中注册不同的组件类型（CATID）。<br /><br /></p><table cellspacing="1" width="83%" border="1"><tbody><tr><td width="22%"><p align="center"><b><span lang="en-us">Band </span>样式</b></p></td><td width="25%"><p align="center"><b>组件类型</b></p></td><td width="51%"><p align="center"><b>CATID</b></p></td></tr><tr><td align="middle" width="22%">垂直的浏览器栏</td><td align="middle" width="25%"><font face="宋体" size="3"><span lang="en-us">CATID_InfoBand</span></font></td><td align="middle" width="51%">00021493-0000-0000-C000-000000000046</td></tr><tr><td align="middle" width="22%">水平的浏览器栏</td><td align="middle" width="25%"><font face="宋体" size="3"><span lang="en-us">CATID_CommBand</span></font></td><td align="middle" width="51%">00021494-0000-0000-C000-000000000046</td></tr><tr><td align="middle" width="22%">桌面的工具栏</td><td align="middle" width="25%"><font face="宋体" size="3"><span lang="en-us">CATID_DeskBand</span></font></td><td align="middle" width="51%">00021492-0000-0000-C000-000000000046</td></tr></tbody></table><br />　　IE 工具栏不使用组件类型注册，而是使用在注册进行 CLSID 的登记方式。详细情况见 3.3。<br />　　在例子程序中，实现了全部四个类型的 band 对象，垂直浏览器栏(CVerticalBar)显示了一个 HTML 文件，并且实现了对 IE 主窗口浏览网页的导航等功能；水平的浏览器栏(CHorizontalBar)是一个编辑窗，它同步显示当前网页的 BODY 源文件内容；IE 工具栏(CToolBar)最简单，只是添加了一个空的工具栏；桌面工具栏(CDeskBar)实现了一个单行编辑窗口，你可以在上面输入命令行或文件名称，回车后它会执行 Shell 的打开动作。<br /><br /><i><a name="3.2　必须实现的_COM_接口">3.2　必须实现的 COM 接口</a></i><br />　　Band 对象是 IE 或 Shell 的进程内服务器，所以它被包装在 DLL 中。而作为 COM 对象，它必须要实现 IUnknown 和 IClassFactory 接口。（大家可以不同操心，因为我们用 ATL 写程序，这两个接口是不用我们自己写代码的。）另外，Band 对象还必须实现 IDeskBand、IObjectWithSite 和 IPersistStream 三个接口：<br />　　<a name="IPersistStream">IPersistStream</a> 是持续性接口的一种。当 IE 加载 band 对象的时候，它通过这个接口的 Load 方法传递属性值给对象，让其进行初始化；而当卸载前，IE 则调用这个接口的 Save 方法保存对象的属性。用 ATL 实现这个接口很简单： <pre>class ATL_NO_VTABLE Cxxx : 
	......
	public IPersistStreamInitImpl, // 添加继承
	......
{
public:
	BOOL m_bRequiresSave; // IPersistStreamInitImpl 所必须的变量
......
BEGIN_COM_MAP(CVerticalBar)
	......
	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
	COM_INTERFACE_ENTRY2(IPersistStream, IPersistStreamInit)
	COM_INTERFACE_ENTRY(IPersistStreamInit)
	......
END_COM_MAP()

BEGIN_PROP_MAP(Cxxx)
...... // 添加需要持续性的属性
END_PROP_MAP()		</pre>　　上面的代码，其实实现的是 IPersistStreamInit 接口，不过没有关系，因为 IPersistStreamInit 派生自 IPersistStream，实例化了派生类，自然就实例化了基类。在例子程序中，我只在桌面工具栏对象中添加了持续性属性，用来保存和初始化“命令行”。另外 COM_INTERFACE_ENTRY2(A，B)表示的含义是：如果想查询A接口的指针，则提供B接口指针来代替。为什么可以这样那？因为B接口派生自A接口，那么B接口的前几个函数必然就是A接口的函数了，自然B接口的地址其实和A接口的地址是一样的了。<br />　　<a name="IObjectWithSite">IObjectWithSite</a> 是 IE 用来对插件进行管理和通讯用的一个接口。必须要实现这个接口的2个函数：SetSite() 和 GetSite()。当 IE 加载 band 对象和释放 band 对象的时候，都要调用 SetSite()函数，那么在这个函数里正好是写初始化和释放操作代码的地方： <pre>STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite)
{
	if( NULL == pUnkSite )	// 释放 band 的时候
	{
		// 如果加载的时候，保存了一些接口
		// 那么现在：释放它
	}
	else	// 加载 band 的时候
	{
		m_hwndParent = NULL;	// 装载 band 的父窗口(就是带有标题的那个框架窗口)

		// 这个窗口的句柄，是调用 IUnknown::QueryInterface() 得到 IOleWindow
		// 然后调用 IOleWindow::GetWindow() 而获得的。
		CComQIPtr&lt; IOleWindow, &amp;IID_IOleWindow &gt; spOleWindow(pUnkSite);
		if( spOleWindow )	spOleWindow-&gt;GetWindow(&amp;m_hwndParent);
		if( !m_hwndParent )	return E_FAIL;
		
		// 现在，正好是建立子窗口的时机。
		// 注意，子窗口建立的时候，不要使用 WS_VISIBLE 属性
		... ...
		// 在例子程序中，用 CAxWindow 实现了一个能包容ActiveX的容器窗口(垂直浏览器栏)
		// 在例子程序中，用 WIN API 函数 CreateWindow 实现了标准窗口(水平浏览器栏、工具栏)
		// 在例子程序中，用 CWindowImpl 实现了一个包容窗口(桌面工具栏)

		/*********************************************************/
		   以下部分，根据 band 对象特有的功能，是可以选择实现的
		**********************************************************/		
		// 如果子窗口实现了用户输入，那么必须实现 IInputObject 接口，
		// 而该接口是被 IE 的 IInputObjectSite 调用的，因此在你的对象
		// 中，应该保存 IInputObjectSite 的接口指针。
		// 在类的头文件中，定义：
		// CComQIPtr&lt; IInputObjectSite, &amp;IID_IInputObjectSite &gt; m_spSite;

		m_spSite = pUnkSite;	// 保存 IInputObjectSite 指针
		if( !m_spSite )		return E_FAIL;

		// 你需要控制 IE 的主框架吗？
		// 那么在类的头文件中，定义：
		// CComQIPtr&lt; IWebBrowser2, &amp;IID_IWebBrowser2 &gt; m_spFrameWB;
		// 然后，先取得 IServiceProvider,再取得 IWebBrowser2

		CComQIPtr &lt; IServiceProvider, &amp;IID_IServiceProvider&gt; spSP(pUnkSite);
		if( !spSP )	return E_FAIL;
		spSP-&gt;QueryService( SID_SWebBrowserApp, &amp;m_spFrameWB );
		if( !m_spFrameWB)	return E_FAIL;

		// 如果你取得了 IE 主框架的 IWebBrowser2 指针
		// 那么，当它发生了什么事情，你难道不想知道吗？
		// 定义：CComPtr m_spCP;

		CComQIPtr&lt; IConnectionPointContainer,
			&amp;IID_IConnectionPointContainer&gt; spCPC( m_spFrameWB );
		if( spCPC )
		{
			spCPC-&gt;FindConnectionPoint( DIID_DWebBrowserEvents2, &amp;m_spCP );
			if( m_spCP )
			{
				m_spCP-&gt;Advise( reinterpret_cast&lt; IDispatch * &gt;( this ), &amp;m_dwCookie );
			}
		}

		// 咳~~~ 不说了，看源码去吧。这里能干的事情太多了... ...
	}
	return S_OK;
}		</pre><a name="IDeskBand">IDeskBand</a> 是一个特殊的 band 对象接口，有一个方法函数：GetBarInfo()；<br />IDockingWindow 是 IDeskBank 的基类，有3个方法函数：ShowDW()、CloseDW()、ResizeBorderDW()；<br />IOleWindow 又是 IDockingWindow 的基类，有2个方法函数：GetWindow()、ContextSensitiveHelp()； <br /><br />　　首先声明 IDeskBand ,然后要实现 IDeskBand 接口的共6个函数，这些函数比较简单，不同类型的 band 对象，其实现方法也都基本一致： <pre>class ATL_NO_VTABLE Cxxx : 
	......
	public IDeskBand,
	......
{
......
BEGIN_COM_MAP(Cxxx)
	......
	COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)
	......
END_COM_MAP()



// IOleWindow
STDMETHODIMP Cxxx::GetWindow(HWND * phwnd)
{	// 取得 band 对象的窗口句柄
	// m_hWnd 是建立窗口时候保存的
	*phwnd = m_hWnd;	
	return S_OK;
}

STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode)
{	// 上下文帮助，参考 IContextMenu 接口
	return E_NOTIMPL;
}

// IDockingWindow
STDMETHODIMP CVerticalBar::ShowDW(BOOL bShow)
{	// 显示或隐藏 band 窗口
	if( m_hWnd )
		::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE);

	return S_OK;
}

STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved)
{	// 销毁 band 窗口
	if( ::IsWindow( m_hWnd ) )
		::DestroyWindow( m_hWnd );

	m_hWnd = NULL;

    return S_OK;
}

STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved)
{	// 当框架窗口的边框大小改变时
	return E_NOTIMPL;
}

// IDeskBand
STDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode,  DESKBANDINFO* pdbi)
{	
         // 取得 band 的基本信息，你需要填写 pdbi 参数作为返回
	if( NULL == pdbi )		return E_INVALIDARG;

	// 如果将来需要调用 IOleCommandTarget::Exec() 则需要保存这2个参数
	m_dwBandID = dwBandID;
	m_dwViewMode = dwViewMode;

	if(pdbi-&gt;dwMask &amp; DBIM_MINSIZE)
	{	// 最小尺寸
		pdbi-&gt;ptMinSize.x = 10;
		pdbi-&gt;ptMinSize.y = 10;
	}

	if(pdbi-&gt;dwMask &amp; DBIM_MAXSIZE)
	{	// 最大尺寸 (-1 表示 4G)
		pdbi-&gt;ptMaxSize.x = -1;
		pdbi-&gt;ptMaxSize.y = -1;
	}

	if(pdbi-&gt;dwMask &amp; DBIM_INTEGRAL)
	{
		pdbi-&gt;ptIntegral.x = 1;
		pdbi-&gt;ptIntegral.y = 1;
	}

	if(pdbi-&gt;dwMask &amp; DBIM_ACTUAL)
	{
		pdbi-&gt;ptActual.x = 0;
		pdbi-&gt;ptActual.y = 0;
	}

	if(pdbi-&gt;dwMask &amp; DBIM_TITLE)
	{	// 窗口标题
		wcscpy(pdbi-&gt;wszTitle,L"窗口标题");
	}

	if(pdbi-&gt;dwMask &amp; DBIM_MODEFLAGS)
	{
		pdbi-&gt;dwModeFlags = DBIMF_VARIABLEHEIGHT;
	}

	if(pdbi-&gt;dwMask &amp; DBIM_BKCOLOR)
	{	// 如果使用默认的背景色，则移除该标志
		pdbi-&gt;dwMask &amp;= ~DBIM_BKCOLOR;
	}

	return S_OK;
}		</pre><i><a name="3.3　选择实现的_COM_接口">3.3　选择实现的 COM 接口</a></i><br />　　有两个接口不是必须实现的，但也许很有用：IInputObject 和 IContextMenu。如果 band 对象需要接收用户的输入，那么必须实现 IInputObject 接口。IE 实现了 IInputObjectSite 接口，当容器中有多个输入窗口时，它调用 IInputObject 接口方法去负责管理用户的输入焦点。<br />在浏览器栏中需要实现3个函数：UIActivateIO()、HasFocusIO()、TranslateAcceleratorIO()。<br />当浏览器栏激活或失去活性的时候，IE 调用 UIActivateIO 函数，当激活的时候，浏览器栏一般调用 SetFocus 去设置它自己窗口的焦点。当 IE 需要判断哪个窗口有焦点的时候，它调用 HasFocusIO 。当浏览器栏的窗口或其子窗口有输入焦点时，则应返回 S_OK，否则返回 S_FALSE。TranslateAcceleratorIO 允许对象处理加速键，例子程序中没有实现，所以直接返回 S_FALSE。 <pre>STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
{
    if(fActivate)
        SetFocus(m_hWnd);

    return S_OK;
}

STDMETHODIMP CExplorerBar::HasFocusIO(void)
{
    if(m_bFocus)
        return S_OK;

    return S_FALSE;
}

STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
{
    return S_FALSE;
}      </pre>　　Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命令。而 IOleCommandTarget 接口指针，则可以通过调用包容器的 IInputOjbectSite::QueryInterface（IID_IOleCommandTarget,...） 函数得到。CGID_DeskBand 是命令组，当一个 band 对象的 GetBandInfo 被调用的时候，包容器通过 dwBandID 参数指定一个 ID 给 band 对象，对象要保存住这个ID，以便调用 IOleCommandTarget::Exec()的时候使用。ID 的命令有： 
<ul><li>DBID_BANDINFOCHANGED<br />Band 的信息变化。设置参数 pvaIn 为 band ID， 该 ID 就是最近一次调用 GetBandInfo 所得到的值，容器会调用 band 对象的 GetBandInfo 函数来更新请求信息。 
</li><li>DBID_MAXIMIZEBAND <br />最大化 band。设置参数 pvaIn 为 band ID，该 ID 就是最近一次调用 ?GetBandInfo ?所得到的值。 
</li><li>DBID_SHOWONLY <br />打开或关闭容器中其它的 bands。 设置参数 pvaIn 为VT_UNKNOWN 类型，它可以是如下的值：<br />　 
<table class="clsStd" width="727" border="1"><tbody><tr><th width="62"><font face="宋体" size="3">值</font></th><th width="650"><font face="宋体" size="3">描述</font></th></tr><tr><td align="middle" width="62"><font face="宋体" size="3">pUnk</font></td><td width="650"><font face="宋体" size="3"><span lang="en-us">band </span>对象的 <b>IUnknown</b> 指针，其它的桌面<span lang="en-us"> bands </span>将被隐藏</font></td></tr><tr><td align="middle" width="62"><font face="宋体" size="3">0</font></td><td width="650"><font face="宋体" size="3">隐藏所有的桌面<span lang="en-us"> bands</span></font></td></tr><tr><td align="middle" width="62"><font face="宋体" size="3">1</font></td><td width="650"><font face="宋体" size="3">显示所有的桌面 <span lang="en-us">bands</span></font></td></tr></tbody></table><br /></li><li>DBID_PUSHCHEVRON<br />在菜单项左边显示“v”的选择标志。容器发送一个 RB_PUSHCHEVRON 消息，当 band 对象接收到通知消息 RBN_CHEVRONPUSHED 提示它显示一个"v"的标志。设置 IOleCommandTarget::Exec 函数中 nCmdExecOpt 参数为 band ID，该 ID 是最近一次调用 GetBandInfo ?所得到的值，设置 IOleCommandTarget::Exec 函数中 pvaIn 参数为 VT_I4 类型，这是应用程序定义的一个值，它通过通知消息 RBN_CHEVRONPUSHED 中lAppValue 回传给 band 对象。 </li></ul><p><i><a name="3.4　Band_对象注册">3.4　Band 对象注册</a></i><br />　　Band 对象必须注册为一个 OLE 进程内的服务器，并且支持 apartment 线程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏，它加到 IE 菜单的“查看\浏览器栏”中；对于工具栏 band ，它加到 IE 菜单的“查看\工具栏”中；对于桌面 band， 它加到系统任务栏的快捷菜单中。在菜单资源中，可以使用“&amp;”指明加速键。<br /><br />通常，一个基本的 band 对象的注册表项目是：<br /><br /><b>HKEY_CLASSES_ROOT <br />CLSID <br /><i>{你的 band 对象的 CLSID}</i></b><br />　　(Default) = 菜单的文字 <br />　　InProcServer32 <br />　　　(Default) = DLL 的全路径文件名 <br />　　　ThreadingModel= Apartment<br /><br />工具栏 bands 还必须把它们的 CLSID 注册到 IE 的注册表中。<br /><br />在 <b>HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar</b> 下给出 CLSID 作为键名，而其键值是被忽略的。<br /><br /><b>HKEY_LOCAL_MACHINE <br />Software <br />Microsoft <br />Internet Explorer <br />Toolbar </b><br />　　{你的 band 对象的 CLSID}<br /><br />　　还有几个可选的注册表项目(例子程序并不是这样实现的)。比如，你想让浏览器栏显示 HTML 的话，必须要如下设置注册表： <br /><br /><b>HKEY_CLASSES_ROOT <br />CLSID <br /><i>{你的 Band 对象的 CLSID} </i><br />Instance <br />CLSID <br />　　</b>(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}<br /><br />同时，如果要指定一个本地的 HTML 文件，那么要如下设置： <br /><br /><b>HKEY_CLASSES_ROOT <br />CLSID <br /><i>{你的 Band 对象的 CLSID}</i><br />Instance <br />InitPropertyBag <br />　　</b>Url<br /><br />　　另外，还可以指定浏览器栏的宽和高，当然，它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓，因为当用户调整了浏览器栏的大小后，会自动保存在注册表中的。<br /><br /><b>HKEY_CURRENT_USER <br />Software <br />Microsoft <br />Internet Explorer <br />Explorer Bars <br />{你的 Band 对象的 CLSID} <br />　　</b>BarSize<br /><br />　　BarSize 键的类型必须是 REG_BINARY 类型，它有8个字节。左起前4个字节，是用16进制表示的像素宽度或高度，后4个字节保留，你应该设置为0。下面是一个可以在浏览器栏上显示 HTML 文件的全部注册表项目的例子，默认宽度为291（0x123）个像素点： <br /><br /><b>HKEY_CLASSES_ROOT <br />CLSID <br /><i>{你的 Band 对象的 CLSID} </i></b><br />　(Default) = 菜单文字 <br />　<b>InProcServer32 </b><br />　　(Default) = DLL 的全路径文件名 <br />　　ThreadingModel= Apartment<br /><b>Instance <br />CLSID </b><br />　　(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}<br /><b>InitPropertyBag</b><br />　　Url= 你的 HTML 文件名<br /><br /><b>HKEY_CURRENT_USER <br />Software <br />Microsoft <br />Internet Explorer <br />Explorer Bars <br />{你的 Band 对象的 CLSID} </b><br />　　BarSize= 23 01 00 00 00 00 00 00<br /><br />　　对于注册表的设置，用 ATL 实现其实是异常简单的。打开工程的 xxx.rgs 文件，并手工编辑一下就可以了。 下面这个文件源码，是例子程序中 IE 工具栏的注册表样式，HKLM 是需要手工添加的，因为它不使用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加： </p><pre>BEGIN_CATEGORY_MAP(Cxxx)			// 向注册表中注册 COM 类型
	IMPLEMENTED_CATEGORY(CATID_InfoBand)	// 垂直样式的浏览器栏
END_CATEGORY_MAP()		</pre>IE 工具栏类型 band 对象的“.rgs”文件<pre>HKCR	// 这个项目是 ATL 帮你生成的，你只要手工修改“菜单上的文字”就可以了
{
	Bands.ToolBar.1 = s ''ToolBar Class''
	{
		CLSID = s ''{ 你的 CLSID }''
	}
	Bands.ToolBar = s ''ToolBar Class''
	{
		CLSID = s ''{ 你的 CLSID }''
		CurVer = s ''Bands.ToolBar.1''
	}
	NoRemove CLSID
	{
		ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&amp;T)''
		{
			ProgID = s ''Bands.ToolBar.1''
			VersionIndependentProgID = s ''Bands.ToolBar''
			ForceRemove ''Programmable''
			InprocServer32 = s ''%MODULE%''
			{
				val ThreadingModel = s ''Apartment''
			}
			''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}''
		}
	}
}

HKLM	// 这个项目是手工添加的IE工具栏所特有的
{
	Software
	{
		Microsoft
		{
			''Internet Explorer''
			{
				NoRemove Toolbar
				{
					ForceRemove val { 你的 CLSID } = s ''随便给个说明性文字串''
				}
			}
		}
	}
}		</pre><b><a name="四、_ATL_实现">四、 ATL 实现</a></b><br />　　下载代码后(VC 6.0 工程)，请参照前面的说明仔细阅读，代码中也有一些关键点的注释。如果想运行，则可以用 regsvr32.exe 进行注册，然后打开 IE 浏览器或资源浏览器就可以看到效果了。如果想自己实践一下，可以按照如下的步骤构造工程：<br /><br />4.1　建立一个 ATL DLL 工程<br />4.2　添加 New ATL Object...，选择 Internet Explorer Object，选这个类型的目的是让向导给我们添加 IObjectWithSite 的支持。如果你使用的是 .net 环境，则不要忘记选择支持这个接口。<br /><br /><img height="257" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands2.jpg" width="413" border="0" /><br /><br />4.3　输入对象名称，比如我想建立一个垂直的浏览器栏，不妨叫它 VerBar<br /><br /><img height="260" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands3.jpg" width="421" border="0" /><br /><br />4.4　线程模型必须选择 Apartment，接口类型的选择无所谓，看你想不想支持 IDispatch 接口功能了。在例子程序中的垂直浏览器栏中，由于想更简单的操纵 IE 和从 IE 中接受事件（连接点），选择 Dual 是必要的。聚合选项，你只要别选择 Only 就可以了。<br /><br /><img height="260" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands4.jpg" width="421" border="0" /><br /><br />4.5　展现你无穷的智慧，开始输入程序吧。如果是 Debug 方式编译，可能会出现一个连接错误，报告找不到_AtlAxCreateControl，那么你要在菜单 Project\Settings...\Link 中增加对 Atl.lib 的连接。或者使用 #pragma comment ( lib, "atl" )加入连接库。<br />4.6　如果想调试代码，在菜单 Project\Settings...\Debug 中输入 IE 的路径名称，比如：“C:\Program Files\Internet Explorer\IEXPLORE.EXE”，然后就可以跟踪断点调试了。 编译和调试桌面工具栏的 band 对象，是非常麻烦的，因为计算机启动时自动运行 Shell，而 Shell 就会加载活动的桌面对象。<br /><br /><b>五、结束语</b><br />好了，到这里，就到这里了。祝大家学习快乐^_^<img src ="http://www.cppblog.com/yishanhante/aggbug/19566.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-11 14:35 <a href="http://www.cppblog.com/yishanhante/articles/19566.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>