使用 .NET 和 Windows Forms 扩展 Explorer 的 Band Objects






4.90/5 (225投票s)
2002 年 4 月 30 日
9分钟阅读

3871783

21146
通过 BandObject 基类,逐步指导你实现一个 Explorer 栏。描述了 BandObject 类的实现细节。
引言
关于使用 Band Objects、Browser Bands、Toolbar Bands 和 Communication Bands 扩展 Windows 和 Internet Explorer 的内容已经有很多了。所以,如果你熟悉 COM 和 ATL,你可能已经自己实现了一个。那么,如果你一直在等待一个简单的方法来给你的朋友留下深刻印象,就像 Google Toolbar 那样,那么它来了 - .NET 方法(或者说 Windows Forms 和 COM Interop 方法?)。在这篇教程中,我将展示如何使用 BandObject
控件创建上述任何一种 Band Object 类型。之后我还会讨论 BandObject
的一些实现细节。
一步一步构建“Hello World Bar”
1.
构建 BandObjectLib 的 Release 版本并将其注册到全局程序集缓存 (GAC)。最简单的方法是使用 Visual Studio 打开 BandObjectLib.sln,将活动配置设置为 Release,然后从“生成”菜单中选择“重新生成解决方案”。解决方案中的第二个项目 - RegisterLib - 是一个 C++ 实用程序项目,它执行“gacutil /if BandObjectLib.dll”命令,将程序集放入 GAC。
正如你可能已经知道的,Band Objects 是 COM 组件。为了让 .NET Framework 找到实现 COM 组件的程序集,它必须要么注册在 GAC 中,要么位于客户端应用程序的目录中。Band Objects 的客户端应用程序有两种可能性 - explorer.exe 和 iexplorer.exe。Explorer 位于 Windows 目录中,而 IE 位于“Program Files”的某个子目录中。所以,在这种情况下,GAC 实际上是唯一的选择。因此,实现 Band Objects 的 .NET 程序集应该注册在 GAC 中,并且它们依赖的所有库 - 如 BandObjectLib.dll - 也应该在那里。
GAC 中的程序集必须具有强名称,因此需要密钥对。我已经提供了带有密钥对的 BandObjects.snk 文件,但我鼓励你用你自己的密钥对替换它。有关更多详细信息,请参阅 sn.exe 工具。
2.
创建一个新的 Windows 控件库项目,并将其命名为 SampleBars。我们将依赖 BandObjectLib 的基本功能,因此我们需要添加对 BandObjectLib\Relase\bin\BandObjectLib.dll 的引用。由于我们正在开发一个“Hello World Bar”,请将 UserControl1.cs 以及其中 UserControl1
类重命名为 HelloWolrdBar.cs 和 HelloWorldBar
。还将以下行放在 HelloWorldBar.cs 的开头:
using BandObjectLib;
using System.Runtime.InteropServices;
3.
让 HelloWorldBar
类继承 BandObject
而不是 System.Windows.Forms.UserControl
。正如我之前提到的,Band Objects 是 COM 组件,所以我们应该使用 Guid
属性。使用 guidgen.exe 生成你的唯一 GUID,或者你可以使用我已经为你生成的 GUID。
[Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")]
我们还必须用强名称签名我们的程序集。你可以通过将以下行放入 AssemblyInfo.cs 文件来完成此操作。
[assembly: AssemblyKeyFile(@"..\..\..\BandObjects.snk")]
4.
现在是时候决定我们要开发哪种类型的 Band Object 了。让我们将其制作成一个 Explorer Toolbar 以及一个 Horizontal Explorer Bar(也称为 Browser Communication Band)。要实现这个决定,我们只需要为我们的 HelloWorldBar
类添加自定义 BandObject
属性。
[Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")]
[BandObject("Hello World Bar",
BandObjectStyle.Horizontal | BandObjectStyle.ExplorerToolbar,
HelpText = "Shows bar that says hello.")]
public class HelloWorldBar : BandObject
{ ...
这足以通过“查看”->“Explorer Bars”和“查看”->“Toolbars”的 Explorer 菜单使我们的控件可用。它还负责菜单项文本 -“Hello World Bar”,并且当菜单项被高亮显示时,状态栏会显示“Shows bar that says hello.”。你不喜欢声明式编程和自定义属性吗?
5.
现在是时候在 Visual Studio Designer 中打开 HelloWorldBar.cs 并添加一些控件了。虽然在我版本的 HelloWorldBar 中,我决定添加一个带有“Say Hello”标题的按钮,但你可以做一些更个性化的事情。我将按钮的大小设置为与控件的客户端区域大小相同,并将其 Anchor
属性设置为所有可能样式的组合 -“Top, Bottom, Left, Right”。背景颜色是“HotTrack”,ForeColor
是“Info”。
BandObject
控件具有几个特定于 Band Objects(以及派生自它的类)的属性 - Title
、MinSize
、MaxSize
和 IntegralSize
。我为 HelloWorldBar
设置了 Title
为“Hello Bar”,并将 MinSize
和 Size
都设置为‘150, 24’。哦,在按钮的 On Click 事件处理程序中,我添加了显示消息框的代码。这是我的最终代码(其中大部分是由 VS.Net 生成的)。
using System;
using System.ComponentModel;
using System.Windows.Forms;
using BandObjectLib;
using System.Runtime.InteropServices;
namespace SampleBars
{
[Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")]
[BandObject("Hello World Bar", BandObjectStyle.Horizontal
| BandObjectStyle.ExplorerToolbar, HelpText = "Shows bar that says hello.")]
public class HelloWorldBar : BandObject
{
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
public HelloWorldBar()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}
base.Dispose( disposing );
}
#region Component Designer generated code
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Anchor = (((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right);
this.button1.BackColor = System.Drawing.SystemColors.HotTrack;
this.button1.ForeColor = System.Drawing.SystemColors.Info;
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(150, 24);
this.button1.TabIndex = 0;
this.button1.Text = "Say Hello";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// HelloWorldBar
//
this.Controls.AddRange(new System.Windows.Forms.Control[] { this.button1 });
this.MinSize = new System.Drawing.Size(150, 24);
this.Name = "HelloWorldBar";
this.Size = new System.Drawing.Size(150, 24);
this.Title = "Hello Bar";
this.ResumeLayout(false);
}
#endregion
private void button1_Click(object sender, System.EventArgs e)
{
MessageBox.Show("Hello, World!");
}
}
}
6.
好的,现在我们已准备好构建 SampleBars.dll,但这还不足以在 Explorer 中看到它。我们必须将我们的程序集放入 GAC 并将其注册为 COM 服务器。有工具 - gacutil.exe 和 regasm.exe - 可以做到这一点。我版本的 SampleBars 解决方案中名为 Register 的 C++ 实用程序项目让我不必手动使用这些工具。它没有任何文件,只有一个如下的生成后命令(调试版本)。
cd $(ProjectDir)..\bin\Debug
gacutil /if SampleBars.dll
regasm SampleBars.dll
当然,你必须确保 Register 项目是解决方案中最后一个被构建的项目,方法是使用项目依赖项/构建顺序。
在构建解决方案并执行 gacutil 和 regasm 命令之后,我们终于可以启动 Explorer 并看到我们的工具栏和 Explorer Bar 了。如果你做的一切都正确,你应该能看到类似于文章顶部图片的内容。在这张图片中,你还可以看到 HelloWorldBar
在 Windows 任务栏中的样子。要实现这一点,你只需要修改 BandObject
属性,添加 BandObjectStyle.TaskbarToolBar
标志。
注意
在开发 band object 时,你可能会遇到几个问题。首先,每次重新生成项目时,Visual Studio 都会生成程序集的新版本。它这样做是因为 AssemblyInfo.cs 中的以下行。
[assembly: AssemblyVersion("1.0.*")]
我建议使用类似“1.0.0.0”的内容,否则你最终会在 GAC 中拥有多个程序集版本。事实上,根据 Jeffrey Richter 的说法,程序集版本自动增量的功能是一个 bug;最初的意图是增量文件的版本,而不是程序集。
另一个问题,更具体地说是 BandObjects 的问题,是 Explorer 会缓存 COM 组件。这意味着即使你将程序集的新版本部署到 GAC,Explorer 在重新启动之前也不会使用它。设置“在单独的进程中启动文件夹窗口”的 Explorer 设置可能会有帮助。另外,如果你收到“Unexpected error creating debug information file...”错误,这也可能是因为你的程序集的上一个版本已加载到 Explorer 的进程空间中。
在 BandObject
内部
好的,现在我们可以查看 BandObject
类的实现。让我们从 ComComInterop.cs 文件开始。Explorer 要求 band objects 实现一组 COM 接口 - IObjectWithSite
、IDeskBand
等。不幸的是,这些接口不像需要的那样以类型库的形式存在,所以你不能仅仅通过添加新的项目引用来使用它们。这些接口声明以 C++ 类和 MIDL 接口的形式提供。所以在 .NET 中使用它们之前,你必须将这些声明转换为一种 .NET 语言。此文件还包含这些接口使用的几个结构和枚举。转换的整个过程非常直接 - 你看到 HWND
,将其替换为 IntPtr
;IUnknown
替换为 Object
等。对我来说,最困难的可能是处理 DESKBANDINFO
结构。你看,在 C++ 版本中,它的一个参数被声明为:
WCHAR wszTitle[256];
经过大量的调试,我才弄清楚 C# 版本应该是什么样子。请注意 CharSet.Unicode
的使用,并且 SizeConst
设置为 255 而不是 256。
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
public struct DESKBANDINFO
{
public UInt32 dwMask;
public Point ptMinSize;
public Point ptMaxSize;
public Point ptIntegral;
public Point ptActual;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
public String wszTitle;
public DBIM dwModeFlags;
public Int32 crBkgnd;
};
因此,BandObject
类实现了所需的接口。入口点是 IDeskBand.GetBandInfo
方法。它的实现只是获取 Size
、MinSize
、IntegralSize
和 Title
属性的值,并填充 DESKBANDINFO
结构。这会给 Explorer 关于如何显示和调整 band object 大小的提示。
IDeskBand
接口其他方法的实现很容易:ShowDW()
委托给 Control.Show()
或 Hide()
;CloseDW()
调用 Dispose()
;而 GetWindow()
只返回控件的 Handle
属性。
IObjectWithSite
是为了与托管的 Explorer 进程建立通信。在 BandObject
控件的 SetSite()
方法中,它尝试获取对 IWebBrowser
接口的引用 - 这是一个由 Explorer 的顶级对象实现的接口。它可以通过 BandObject.Explorer
属性获得。对于任务栏工具栏,没有 IWebBrowser
接口,所以它会优雅地处理这种情况。一旦检索到 IWebBrowser
的指针,BandObject
就会触发 ExplorerAttached
事件。处理此事件在你想添加额外的初始化代码时非常有用 - 订阅 Web Browser 事件等。
IInputObject
的实现更有趣。如果你希望你的 band object 参与处理键盘输入,则需要此接口。用户可以通过按“Tab”或“Shift+Tab”键在不同的 Explorer 界面对象(地址栏、文件夹视图)之间导航。当轮到你的 band object 被激活或停用时,Explorer 会调用 UIActivateIO()
方法。BandObject
的实现只是调用其子控件之一的 Select()
以获取焦点。选择哪个控件取决于控件的 Tab 顺序以及用户是向前导航还是向后导航(按 Shift 键)。BandObject
还确保焦点可以离开它(以防最后一个控件被选中并且用户按下 Tab)。此逻辑在 TranslateAcceleratorIO()
方法中实现。它首先检查是否按下了“Tab”或“F6”键。然后,根据“Shift”键的状态,它尝试使用 SelectNextControl()
方法在子控件之间移动焦点。SelectNextControl
的最后一个参数是 false
,因为我们不希望控件永远循环(从最后一个到第一个,反之亦然)。如果没有下一个控件,我们返回零,这会向 Explorer 信号表示我们不知道如何处理此命令。因此,Explorer 本身会处理该命令,将焦点移动到适当的用户界面对象。
最后是我最喜欢的部分:Register()
和 Unregister()
方法。这些方法都带有 Com{Un}registerFunction
属性,因此 regasm.exe 工具知道在注册程序集作为 COM 服务器时调用它们。Register
函数会检查其输入参数是否存在 BandObjectAttribute
。如果存在,则使用其 Name
、Style
和 HelpText
属性来创建适当的注册表设置。例如,要使你的 COM 组件被视为“Browser Communication Band”,你必须将其标记为实现了“Browser Communication Band”COM 类别。要将其制作成 Explorer 工具栏,你必须在 SOFTWARE\Microsoft\Internet Explorer\Toolbar 下注册其 CLSID
。但是你不需要担心这些细节。你只需要知道使用哪个 BandObjectStyle
标志,Register()
就会处理其余的事情。类似地,Unregister()
方法会处理在注销程序集时需要从注册表中删除的内容。