在第13篇里,我说过我要给你们展示一些菜单、工具栏和命令的示例代码,但是相关的东西太多了,一篇文章没办法全部囊括,所以在这里我只涉及一些和vsct文件相关的代码。
前言
在本系列前面的文章里,我多次提到过Package是按需加载的,IDE只有在真正用到Package的时候才去加载它们。但这样就带来一个问题:IDE如何在不加载Package的情况下,显示Package里定义的菜单和工具栏?或者说当我们在IDE里看到某个Package的菜单的时候,这个Package到底有没有被加载到IDE里?
当Package被注册后(通过regpkg.exe),Package里代表菜单和工具栏的资源实际上是被单独存放在一个地方,所以在Visual Studio启动后,它就可以从这个地方读取出这个信息,并显示相应的菜单和工具栏,而不必加载Package本身。
实现这种模式的关键,就在于vsct(Visual Studio Command Table)文件。这个文件是旧版的Visual Studio SDK中ctc文件的替代品,是用来定义命令以及与命令相关的UI的。编译完package后,vsct文件被编译到一个cto文件中,并且作为一个资源添加到package的dll里。
在VS 2005版本的Visual Studio SDK里,用的是文本格式的ctc文件。编辑和理解ctc文件并不是一个简单的任务。所以在Visual Studio 2008 SDK里,微软创建了一种新的基于xml的vsct文件和相应的编译器,负责把vsct编译成cto格式。
应用vsct文件的最大的优势在于它就是一个xml文件,它拥有xml文件固有的特性,例如自动生成结束标签或者智能感知。所以微软建议我们用vsct文件来代替ctc文件,当然,ctc文件目前依然可以使用。
在这篇文章里,我将给大家介绍一些在前面的文章里没有提到的关于vsct文件的细节。首先我要先介绍一下vsct文件的基础,然后再用一些例子来说明它。
VSCT文件的结构
xml文件的xsd架构可以告诉我们一个xml文件里应该包含什么内容,因为xsd文件里定义了相应的xml文件的语法、词法等很多信息,并且“据说”不管是对计算机还是对人来说,可读性都很好。毫无疑问,计算机可以很牛逼的识别xsd文件,但对于我们人类来说,其实它的可读性并不是那么好。所以在这里我就不给大家展示vsct的xsd架构了,而是通过例子来解释一下vsct的结构。
外层节点
VSCT文件的根结点是CommandTable:
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/..."
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Content of the command table -->
</CommandTable>
command table的元素的命名空间是“http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable”,不要忘了加上它,但是我为了使示例代码更短,所以不加这个命名空间。
CommandTable的子节点是:
<CommandTable xmlns="..." xmlns:xs="...">
<Extern/>
<Include/>
<Define/>
<Commands/>
<CommandPlacements>
<VisibilityConstraints/>
<KeyBindings/>
<UsedCommands/>
<Symbols/>
</CommandTable>
其中,最重要的是Extern、Commands和Symbols节点,让我们看一下VSPackage向导帮我们生成的vsct文件:
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="..." xmlns:xs="...">
<Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>
<Extern href="vsshlids.h" mce_href="vsshlids.h"/>
<Extern href="msobtnid.h" mce_href="msobtnid.h"/>
<Commands package="guidSimpleCommandPkg">
<Groups>
<Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
</Groups>
<Buttons>
<Button guid="guidSimpleCommandCmdSet" id="cmdidMyFirstCommand"
priority="0x0100" type="Button">
<Parent guid="guidSimpleCommandCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<CommandName>cmdidMyFirstCommand</CommandName>
<ButtonText>My First Command</ButtonText>
</Strings>
</Button>
</Buttons>
<Bitmaps>
<Bitmap guid="guidImages" href="Resources\Images_32bit.bmp" mce_href="Resources\Images_32bit.bmp"
usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
</Bitmaps>
</Commands>
<Symbols>
<GuidSymbol name="guidSimpleCommandPkg"
value="{2291da24-92e5-4ea4-bdb7-72a9b5ac7d59}" />
<GuidSymbol name="guidSimpleCommandCmdSet"
value="{a982b107-4ad4-437e-b2bc-cdf2708aa376}">
<IDSymbol name="MyMenuGroup" value="0x1020" />
<IDSymbol name="cmdidMyFirstCommand" value="0x0100" />
</GuidSymbol>
<GuidSymbol name="guidImages" value="{5c3faf04-8190-48c4-a6e9-71f04f1848e5}" >
<IDSymbol name="bmpPic1" value="1" />
<IDSymbol name="bmpPic2" value="2" />
<IDSymbol name="bmpPicSearch" value="3" />
<IDSymbol name="bmpPicX" value="4" />
<IDSymbol name="bmpPicArrows" value="5" />
</GuidSymbol>
</Symbols>
</CommandTable>
Symbols和IDs
我在第6篇里已经说过了,VS IDE里的对象(例如命令和相关的UI元素)都有唯一的标识,我们需要利用他们的唯一标识符来引用某个元素。标识符由一个GUID和一个32位无符号整数组成。GUID代表一个逻辑上包含对象的容器,32位无符号整数代表这个对象在逻辑容器内的id。(也有一些对象只用GUID来标识,稍后我会介绍它们)。
在vsct文件里,如果直接用这些GUID和ID的话,代码的可读性就太差了,幸好vsct文件里有Symbols节点。该节点用于给这些GUID或者ID起个可读性较好的名字,其中:GuidSysmbol子节点用来给逻辑容器GUID起别名,嵌套的IDSymbol节点用来给32位无符号数起别名。
在上面的代码段中,定义了三个GUID容器。第一个是一个空的容器(别名是guidSimpleCommandPkg),另外两个则包含若干个ID。定义好了symbols之后,就可以引用它们了:
<Commands package="guidSimpleCommandPkg">
<Groups>
<Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">
<!-- ... -->
</Group>
</Groups>
<Buttons>
<Button guid="guidSimpleCommandCmdSet" id="cmdidMyFirstCommand"
priority="0x0100" type="Button">
<!-- ... -->
</Button>
</Buttons>
<Bitmaps>
<Bitmap guid="guidImages" href="Resources\Images_32bit.bmp" mce_href="Resources\Images_32bit.bmp"
usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
</Bitmaps>
</Commands>
在定义命令的时候,我们经常需要引用VS IDE里已经定义好的菜单。例如,如果想在“工具”菜单下添加子菜单,我们必须引用已经定义在VS IDE里的“工具”这个菜单。当然, “工具”菜单和其他菜单(包括我们自己的菜单)的定义方式是一样的:
<Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
Parent子节点指定了由Group元素定义的逻辑容器应该被放在哪个位置。guidSHLMainMenu 代表VS IDE的主菜单的逻辑容器,IDM_VS_MENU_TOOLS 表示“工具”菜单项的ID。也许你已经猜到了,有上千个和VS IDE相关的GUID和ID。
可以用Extern节点来访问它们:
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="..." xmlns:xs="...">
<Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>
<Extern href="vsshlids.h" mce_href="vsshlids.h"