前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(翻译)LearnVSXNow!-#6 创建我们第一个工具集-序幕

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

作者头像
明年我18
发布2019-09-18 11:32:37
4790
发布2019-09-18 11:32:37
举报
文章被收录于专栏:明年我18明年我18

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

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

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

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

image
image

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

创建一个空的VSPackage

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

image
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:

代码语言:javascript
复制
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并写入如下代码:

代码语言:javascript
复制
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,写入如下代码:

代码语言:javascript
复制
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设定标识符:

代码语言:javascript
复制
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的值了,例如:

代码语言:javascript
复制
1: <Bitmap guid="guidImage" href="Resources\Clock.bmp" usedList="bmpPic1"/>

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

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

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

代码语言:javascript
复制
1: <Commands package="guidStartupToolsetPkg">   2:   ...   3: </Commands>

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

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

代码语言:javascript
复制
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

定义该命令的显示文本。

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

代码语言:javascript
复制
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,并且可以设置这个菜单的版本号,代码如下:

代码语言:javascript
复制
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文件的节点。如下:

代码语言:javascript
复制
1: <ItemGroup>   2:     <None Include="StartupToolset.vsct" />    3: </ItemGroup>

修改代码为:

代码语言:javascript
复制
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了)

代码语言:javascript
复制
1: ...   2: [ProvideMenuResource(1000, 1)]    3: public sealed class StartupToolsetPackage: Package { ... }   4: ...

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

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

代码语言:javascript
复制
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文件中用于定义菜单项的一样。

代码语言:javascript
复制
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
image

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

image
image

总结

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

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2010-03-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建一个空的VSPackage
  • 手动添加新的菜单项
  • 什么是command table configuration文件?
  • 第一步:增加一个command ID
  • 第二步:建立.vsct文件
  • 第三步:为package类添加ProvideMenuResourceAttribute
  • 第四步:设置vsct文件的编译选项
  • 第五步:创建命令处理方法
  • 第六步:把事件处理方法和命令关联起来
  • 尝一尝布丁吧!
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档