240
一线老司机

(翻译)LearnVSXNow!-#6 创建我们第一个工具集-序幕

     在前面的文章中,我们在向导的帮助下创建了一些小的VSPackages。在第五讲中我们整理了VSX的一些思路和概念,深入了解了packages是如何工作的以及服务的机制。在这篇文章中我们继续前进。

      为了创建创建“容易编写和理解”的代码,从本文开始,我们开始创建一个工具集示例Package。我计划用至少如下三个主题来讨论:

  1. 序幕:创建示例package的第一部分,它是这个工具集的基础。在这篇中我们将手动添加菜单命令来探讨一下command table configuration文件。
  2. 完成示例:在这篇文章里,我们创建示例package的第二部分。手动添加一个自定义Tool Window,并且探索一下output window。
  3. 重构:我们修改package,提取一些在package开发中公共的可复用的类型。

      在这个系列中,我们会创建一个工具窗,它可以对两个整数进行算术运算。

image

     写这个系列的目的,并不是为了实现这个工具集的功能,而是为了熟悉创建类似应用的步骤。通过创建这个简单的工具集,可以使我们更熟悉package的开发,这要比直接讲解VS SDK中的interop程序集和MPF类更容易理解。

创建一个空的VSPackage

     我们先创建一个空的VSPackage。因为在前面的文章中我说明了创建空package的步骤,所以在这里就省略掉截图了。选择Visual Studio Integration Package类型的项目,该项目模板会弹出我们的朋友—VSPackage向导。命名工程为StartupToolset。选择C#语言,根据下面的图片填写基本的信息:

image

     在下一个向导页面不要勾选Menu command, Tool window 和 Command editor中的任何一个(因为我们要手动添加它们);再下一步也不要勾选任何测试项目,最后点击完成。向导生成了一个空package的项目。运行后检查Help|About对话框,以确认StartupToolset包是否在VS实验室环境下被正确的注册了。(注意:为了减少代码量和提高可读性,这个时候我删除了向导生成的注释,你当然也可以这么做,但这些注释有利于理解代码的含义,很值得一读)

     在前面的文章中我们通过向导添加了菜单命令和工具窗口。在这个例子中我们将手动添加。

手动添加新的菜单项

     为了显示一个菜单项,我们要这样做:

  1. 为命令创建一个ID、名字和显示的文本,该命令用于显示tool window
  2. 创建.vsct文件来设置所谓的command table configuration
  3. 为package类添加ProvideMenuAttribute
  4. 设置.vsct文件的Build Action
  5. 创建菜单项的事件处理函数
  6. 建立命令和该事件处理函数的关联

什么是command table configuration文件?

     在之前的文章中,我提到过VSPackages是“按需加载(on-demand loaded)”的,当packages中的对象将要被创建,或者其中的服务将要被使用的时候IDE才将他们装载进内存。这听起来不错,不过有个问题:如果对象表示了菜单或者工具栏对象,并且和package的源代码编译在一起,那么IDE不得不仅仅为了展示这些UI而加载这个package,哪怕这个package并没有被使用。为了显示这些跟package相关的菜单和工具栏(而避免上述情况的发生),这些对象被设计成package的二进制资源。当package被注册(通过regpkg.exe)时,这些资源被提取并分开存放,这样Visual Studio就可以在不加载package的情况下显示这些资源。

     command table configuration文件是要实现这个策略的关键。这个文件的职责是定义与命令相关的UI元素。当我们编译一个package时,command table configuration文件转换成一个cto文件(command table output file),并作为一个资源,编译到package中。

     在vs2005版本的VS SDK中,使用一种文本形式的command table configuration文件(.ctc后缀)。理解和编辑.ctc文件不是件容易的事。随着Visual Studio 2008 SDK的发布,微软创建了一种基于XML的文件格式(.vsct: Visual Studio Command Table),并且配以一种新的编译器(VSCTCompile)来将.vsct文件编译成.cto文件。

     vsct文件主要的优点是它像其他xml文件一样,很容易编辑,并且沿袭了XML所有的好的特性,比如自动生成结束标签和基于vsct XML 架构的智能感知。尽管仍然可以使用ctc文件,但微软推荐使用vsct文件。

第一步:增加一个command ID

     为Command指定ID的目的,是为了将这个package里的命令项和Visual Studio中的命令项或其他package中的加以区分。Command是以ID作为标识的UI相关的对象,就像菜单项或者bitmaps那样。UI相关对象的ID是分层次的,由一个GUID和32位无符号整数组成。GUID表示逻辑上拥有这些UI对象的容器,而32位无符号数则用来在容器内部区分不同的对象。

     向导生成的Guids.cs文件包含了一个用于标识package的GUID和一个用于标识命令集(command set)的GUID:

   1: using System; 
   2: namespace MyCompany.StartupToolset
   3: { 
   4:       static class GuidList 
   5:       { 
   6:             public const string guidStartupToolsetPkgString = "1376bfe2-5278-493d-867e-2b5ba828368d"; 
   7:             public const string guidStartupToolsetCmdSetString = "ec3d3ea6-2261-4a18-a458-78591688e06d"; 
   8:             public static readonly Guid guidStartupToolsetCmdSet = new Guid(guidStartupToolsetCmdSetString); 
   9:       } 
  10: }

     我们要显示的菜单项是从属于command set容器中的一个对象,所以我们还需要在command set容器内部,用一个32位无符号数来标识我们的菜单项。我们把这个ID作为一个常量放在一个新的文件PkgCmdID.cs中(这个文件名的命名是根据惯例来命名的,如果在向导中勾选了Menu Command的话,向导也会生成这么一个文件)

     新建一个PkgCmdID.cs并写入如下代码:

   1: using System; 
   2: using System.Collections.Generic; 
   3: using System.Linq; 
   4: using System.Text; 
   5: namespace MyCompany.StartupToolset 
   6: { 
   7:       static class PkgCmdIDList 
   8:       { 
   9:             public const uint cmdidCalculateTool = 0x101; 
  10:       } 
  11: } 

第二步:建立.vsct文件

     vsct文件用来定义command table configuration,它是XML格式的。为了显示一个菜单项,我们必须创建一个vsct文件,定义用户对象和所需的资源,并且与代码绑定以实现相关的行为。在以后的文章中,我会非常详细地解释vsct文件的格式和用法,但这一次我们只是简单的看一下它。

     因为我们创建的是一个空的package,所以向导没有创建任何command table文件,我们需要手动添加一个StartupToolset.vsct文件。在“添加新项”对话框中选择XML文件,并命名为StartupToolset.vsct,写入如下代码:

   1: <?xmlversion="1.0" encoding="utf-8"?> 
   2: <CommandTable xmlns= 
   3:       "http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" 
   4:       xmlns:xs="http://www.w3.org/2001/XMLSchema"> 
   5:       <Extern href="stdidcmd.h"/> 
   6:       <Extern href="vsshlids.h"/> 
   7:       <Extern href="msobtnid.h"/> 
   8:       <Commands package="guidStartupToolsetPkg"> 
   9:             <Buttons> 
  10:                   <Button guid="guidStartupToolsetCmdSet" id="cmdidCalculateTool" 
  11:                         priority="0x0100" type="Button"> 
  12:                         <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1"/> 
  13:                         <Icon guid="guidImage" id="bmpPic1"/> 
  14:                         <Strings> 
  15:                               <CommandName>cmdidCalculateTool</CommandName> 
  16:                               <ButtonText>Calculate Tool Window</ButtonText> 
  17:                         </Strings> 
  18:                   </Button> 
  19:             </Buttons> 
  20:             <Bitmaps> 
  21:                   <Bitmap guid="guidImage" href="Resources\Clock.bmp" usedList="bmpPic1"/> 
  22:             </Bitmaps> 
  23:       </Commands> 
  24:       <Symbols> 
  25:             <GuidSymbol name="guidStartupToolsetPkg" 
  26:                   value="{1376bfe2-5278-493d-867e-2b5ba828368d}"/> 
  27:             <GuidSymbol name="guidStartupToolsetCmdSet" 
  28:                   value="{ec3d3ea6-2261-4a18-a458-78591688e06d}"> 
  29:                   <IDSymbol name="cmdidCalculateTool" value="0x0101"/> 
  30:             </GuidSymbol> 
  31:             <GuidSymbol name="guidImage" value="{91CB158E-29BC-4818-8C1F-967AF94D96B1}">
  32:                   <IDSymbol name="bmpPic1" value="1"/> 
  33:             </GuidSymbol> 
  34:       </Symbols> 
  35: </CommandTable>

(译者注:作者并没有说明图片资源“Clock.bmp”是怎样做出来的,读者可以从之前的示例项目(例如SimpleToolWindow项目)中复制一个图片(如Images_32bit.bmp)过来)

     .vsct文件的根元素是CommandTable,指定了名字空间和XML架构。

     我之前提到过,对象的标识是由GUID和<GUID,数字>对来定义的。在CommandTable中我们必须涉及到在Visual Studio中使用的对象标识,Extern元素允许从外部文件(头文件)加载这些ID。在这个CommandTable中我们使用了如下头文件:

文件 内容

stdidcmd.h

这个文件包含了Visual Studio公开的所有命令的ID。可见的(和不可见的)菜单项的ID以cmdid 开头,标准编辑器命令以ECMD_ 开头等。

vsshlids.h

这个文件包括了Visual Studio外壳提供的菜单命令的ID。由于命令的标识包含GUID,所以在这个文件中能找到一些guid 开头的"宏",Command标识中的无符号整数部分,则以IDM_VS、IDG_VS或一些其他的前缀开头。

msobtnid.h

这个文件表示在Microsoft Office 中用到的命令的ID。

 

     这些头文件可以在VS 2008 SDK安装目录的VisualStudioIntegration\Common\Inc子目录中找到。

     我们的package定义了自己的GUID和命令的ID,并且可能在.vsct 文件中多次使用到这些值。为了使vsct文件定义更简单并减少打字错误,我们可以在command table中增加Symbols节点,为这些GUID和命令ID设定标识符:

   1: <Symbols> 
   2:       <GuidSymbol name="guidStartupToolsetPkg" 
   3:             value="{1376bfe2-5278-493d-867e-2b5ba828368d}"/> 
   4:       <GuidSymbol name="guidStartupToolsetCmdSet" 
   5:             value="{ec3d3ea6-2261-4a18-a458-78591688e06d}"> 
   6:             <IDSymbol name="cmdidCalculateTool" value="0x0101"/> 
   7:       </GuidSymbol> 
   8:       <GuidSymbol name="guidImage" value="{91CB158E-29BC-4818-8C1F-967AF94D96B1}"> 
   9:             <IDSymbol name="bmpPic1" value="1"/> 
  10:       </GuidSymbol> 
  11: </Symbols>

     这样我们就可以用这些符号名而不是直接使用ID的值了,例如:

   1: <Bitmap guid="guidImage" href="Resources\Clock.bmp" usedList="bmpPic1"/>

     如你所见,GuidSymbol元素(用于定义逻辑容器的ID)可以包含IDSymbol元素(用于定义在容器内部的元素的ID)。

     现在,我们可以利用这些ID来定义界面的相关对象了。

     vsct文件用于定义命令,这些命令全部定义在Commands节点内。通过前面的文章我们可以知道,这些命令属于同一个package。Commands节点的package属性指定了这个package的ID:

   1: <Commands package="guidStartupToolsetPkg">
   2:   ...
   3: </Commands>

     为了定义一个命令,Commands节点下可以包含子节点,比如Groups、Buttons、Bitmaps等等。例如,如果我们要定义一个和命令相关的菜单项,我们可以把该菜单组定义在Groups下面的Group节点上,把菜单项定义在Buttons下面的Button节点上,把和该菜单相关的图片定义在Bitmaps节点内。

     在我们的vsct文件内,我们通过下面的代码段来定义我们的菜单项:

   1: <Buttons>
   2:   <Button guid="guidStartupToolsetCmdSet" id="cmdidCalculateTool"
   3:     priority="0x0100" type="Button">
   4:     <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1"/>
   5:     <Icon guid="guidImage" id="bmpPic1" />
   6:     <Strings>
   7:       <CommandName>cmdidCalculateTool</CommandName>
   8:       <ButtonText>Calculate Tool Window</ButtonText>
   9:     </Strings>
  10:   </Button>
  11: </Buttons>

     在上面的代码段中,我们定义了一个菜单项,它的type属性是Button,并且用了在Symbol节点下定义的guid-id对作为标识。Button节点有一些子节点,这些子节点定义了该菜单项的一些属性:

节点 描述
Parent

     该节点表示按钮的父亲。一个按钮可以有一个或多个父亲,在界面上看,该按钮代表的命令可以放在多个地方。例如,可以同时把它放在主菜单、工具栏或右键菜单里。

     在这个例子中,guidSHLMainMenu是Visual Studio主菜单的逻辑容器的标识,IDG_VS_WNDO_OTRWNDWS1是菜单项“视图|其他窗口”的ID。

Icon

定义与命令相关的图标。

Strings\CommandName

定义命令的名字,可以通过命令的名字来查找命令。

Strings\ButtonText

定义该命令的显示文本。

 

     让我们看一下这个命令的图标是怎样定义的:

   1: <Bitmaps>
   2:   <Bitmap guid="guidImage" href="Resources\Clock.bmp" mce_href="Resources\Clock.bmp" 
   3:      usedList="bmpPic1"/>
   4: </Bitmaps>

     图标、图片或bitmap定义在Bitmaps节点下。一个Bitmap节点定义一个bitmap  strip。属性guid代表这个bitmap strip的ID,href属性表示该图片相对于项目所在目录的相对路径。usedList属性的值代表了bitmap strip中的bitmap ID,以逗号隔开。这些bitmap strip中的bitmap ID定义在GuidSymbol节点中代表bitmap的IDSymbol子节点中。Bitmap strip ID是从1开始的(1,2,3…),如果我们想用bitmap strip中的一个bitmap,我们可以把usedList属性的值设置为相应的ID值。(译者注:有关bitmap strip的概念,可以Google一下或参考这篇文章:http://www.axialis.com/tutorials/image-strip.html)

第三步:为package类添加ProvideMenuResourceAttribute

     为了保证regpkg.exe能注册我们的菜单,我们必须为package类添加ProvideMenuResourceAttribute。这个attribute指定了保存有这个菜单和命令信息的资源ID,并且可以设置这个菜单的版本号,代码如下:

   1: ...
   2: [ProvideMenuResource(1000, 1)] 
   3: public sealed class StartupToolsetPackage: Package { ... }
   4: ...

     为什么我们要把资源ID指定为1000?你很快就会知道答案…

 第四步:设置vsct文件的编译选项

     在这篇文章的开头,我讲了一下Command Table Configuration文件的职责,并且提到了vsct文件被编译到二进制资源中。当我们为项目添加StartupToolset.vsct文件后,该文件的生成动作(Build Action)默认是None

     为了能把vsct文件编译到资源中,应该设置Build Action为VSCTCompile(如果我们用VSPackage向导并选择Menu Command的话,这个文件会自动设置成VSCTCompile)。

     在这里会遇到Visual Studio的一个问题(更确切的说是Visual Studio 2008 SDK第一版的问题)。当我们试图把Build Action改成VSCTCompile时,我们会发现在Build Action的下拉列表里根本没这个选项!如果我们手动敲入这个值时,会得到一个Invalid Property的错误。这其实是一个bug。

     对于这个bug,我找到了一些解决办法。最稳妥的(也是最直接的)办法是手动修改.csproj文件。

     用文本编辑器(例如记事本)打开这个项目文件,然后找到有关StartupToolset.vsct文件的节点。如下:

   1: <ItemGroup>
   2:     <None Include="StartupToolset.vsct" /> 
   3: </ItemGroup>

     修改代码为:

   1: <ItemGroup>
   2:   <VSCTCompile Include="StartupToolset.vsct">
   3:     <ResourceName>1000</ResourceName>
   4:   </VSCTCompile> 
   5: </ItemGroup>

     使用VSCTCompile节点会正确的设置build action。ResourceName子节点使得编译器在编译过程中,用1000作为资源ID,把cto文件编译到VSPackage中。这样就会确保regpkg.exe能够利用ProvideMenuResource来正确的注册package中的菜单:(译者注:从这里我们就知道ProvideMenuAttribute的第一个参数为什么是1000了)

   1: ...
   2: [ProvideMenuResource(1000, 1)] 
   3: public sealed class StartupToolsetPackage: Package { ... }
   4: ...

  第五步:创建命令处理方法

     到目前为止,我们还没有创建工具窗来测试新创建的菜单,在这里可以简单的显示一个消息来代替工具窗。在StartupToolsetPackage类里,我们添加一个私有的事件处理方法:

   1: ...
   2: public sealed class StartupToolsetPackage : Package
   3: {
   4:   ...
   5:   private void ShowCalculateToolCallback(object sender, EventArgs e)
   6:   {
   7:     MessageBox.Show("Calculate Tool Window is about to be displayed...",
   8:       "Tool Window");
   9:   }
  10:   ...
  11: }

第六步:把事件处理方法和命令关联起来

     在这里,我们采用和前面几篇文章中(SimpleCommandSimpleToolWindow)差不多的代码。把事件处理方法和命令关联起来的代码写在package类的Initialize方法中,并且用到的<GUID,ID>对要和vsct文件中用于定义菜单项的一样。

   1: protected override void Initialize()
   2: {
   3:   Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, 
   4:     "Entering Initialize() of: {0}", this.ToString()));
   5:   base.Initialize();
   6:   
   7:   OleMenuCommandService mcs = 
   8:     GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
   9:   if (null != mcs)
  10:   {
  11:     CommandID menuCommandID = new CommandID(GuidList.guidStartupToolsetCmdSet, 
  12:       (int)PkgCmdIDList.cmdidCalculateTool);
  13:     MenuCommand menuItem = new MenuCommand(ShowCalculateToolCallback,
  14:       menuCommandID);
  15:     mcs.AddCommand(menuItem);
  16:   }
  17: }

尝一尝布丁吧!

     完成上面这一步后,我们就创建好了一个package,它包含一个手动创建的菜单,点击这个菜单会弹出一个消息框。编译并且运行这个项目,当vs 2008 Experimental Hive启动后,你可以在菜单“视图|其他窗口”里看到我们的菜单项:

image

     点击“Calculate Tool Window”菜单项,会弹出一个消息框:

image

 

总结

     这这一篇中,我们开始创建一个工具集来熟悉VSPackage的开发。作为这个系列的第一部分,我们创建了一个空的package,并手动添加了一个菜单命令。在这个过程中,我们探讨了Visual Studio Command Table文件在描述UI资源时的作用。

     在设置vsct文件的build action时,我们发现了关于Build Action属性的一个bug。通过手动的修改.csproj文件,可以绕开这个bug。

     在下一篇里,我们将手动创建一个工具窗,并添加简单的功能。

 

原文链接:http://dotneteers.net/blogs/divedeeper/archive/2008/01/14/LearnVSXNowPart6.aspx

 

题外话:

     这篇译文参考了P_Chou的译文,你可以点击这里看他的原文。这些图片也都是他截的,在这里对他表示感谢!

     另外,我建立了VSX的团队(http://vsx.cnblogs.com/),欢迎有兴趣的朋友加入,让我们互相学习和探讨VSX的开发。

posted @ 2010-03-08 23:52  明年我18  阅读(3491)  评论(15编辑  收藏  举报