65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (225投票s)

2002 年 4 月 30 日

9分钟阅读

viewsIcon

3871783

downloadIcon

21146

通过 BandObject 基类,逐步指导你实现一个 Explorer 栏。描述了 BandObject 类的实现细节。

Sample Image - dotnetBandObjects.jpg

引言

关于使用 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.csHelloWorldBar。还将以下行放在 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(以及派生自它的类)的属性 - TitleMinSizeMaxSizeIntegralSize。我为 HelloWorldBar 设置了 Title 为“Hello Bar”,并将 MinSizeSize 都设置为‘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.exeregasm.exe - 可以做到这一点。我版本的 SampleBars 解决方案中名为 Register 的 C++ 实用程序项目让我不必手动使用这些工具。它没有任何文件,只有一个如下的生成后命令(调试版本)。

        cd $(ProjectDir)..\bin\Debug
        gacutil /if SampleBars.dll
        regasm SampleBars.dll
        

当然,你必须确保 Register 项目是解决方案中最后一个被构建的项目,方法是使用项目依赖项/构建顺序。

在构建解决方案并执行 gacutilregasm 命令之后,我们终于可以启动 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 接口 - IObjectWithSiteIDeskBand 等。不幸的是,这些接口不像需要的那样以类型库的形式存在,所以你不能仅仅通过添加新的项目引用来使用它们。这些接口声明以 C++ 类和 MIDL 接口的形式提供。所以在 .NET 中使用它们之前,你必须将这些声明转换为一种 .NET 语言。此文件还包含这些接口使用的几个结构和枚举。转换的整个过程非常直接 - 你看到 HWND,将其替换为 IntPtrIUnknown 替换为 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 方法。它的实现只是获取 SizeMinSizeIntegralSizeTitle 属性的值,并填充 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。如果存在,则使用其 NameStyleHelpText 属性来创建适当的注册表设置。例如,要使你的 COM 组件被视为“Browser Communication Band”,你必须将其标记为实现了“Browser Communication Band”COM 类别。要将其制作成 Explorer 工具栏,你必须在 SOFTWARE\Microsoft\Internet Explorer\Toolbar 下注册其 CLSID。但是你不需要担心这些细节。你只需要知道使用哪个 BandObjectStyle 标志,Register() 就会处理其余的事情。类似地,Unregister() 方法会处理在注销程序集时需要从注册表中删除的内容。

© . All rights reserved.