专栏首页明年我18(翻译)LearnVSXNow!-#3 创建一个带有简单命令的Package

(翻译)LearnVSXNow!-#3 创建一个带有简单命令的Package

为了演示如何给我们的package增加功能,本篇将创建一个带有简单菜单(命令)的VS Package。和上一篇一样,我们新建一个Visual Studio Integration Package类型的项目,这一次我们把它命名为SimpleCommand。当项目向导出现后,我们选择C#做为开发语言,并利用向导为我们的程序集自动生成一个key文件。在VSPackage Information页面,我们输入如下内容:

在下一步,为了创建一个简单的菜单命令,我们选中Menu Command:

当转到下一步的时候,向导会要求我们填写菜单的显示文本和菜单的标识,请参考下图填写:

在向导的最后一步我们可以建立集成测试项目和单元测试项目,请勾掉这两个选项并且点击Finish按钮。向导会在几秒钟内帮我们创建项目的源文件。

编译并运行SimpleCommand项目。当Visual Studio实验室运行后,你可以在工具菜单下发现我们的package的菜单命令:

点击菜单My First Command,可以看到一个消息框。证明这个package已经正常运行了。

源文件分析(What is inside?)

关掉VS实验室,让我们查看一下源文件。我们可以发现,与上一篇的EmptyPackage相比,这一次多了两个文件:PkgCmdID.csSimpleCommand.vsct。在文件PkgCmdID.cs中定义了菜单“My First Command”的标识符:

1: //在英文原文中,命名空间是MyCompany.SimpleToolWindow   2: namespace MyCompany.SimpleCommand   3: {   4:   static class PkgCmdIDList   5:   {   6:     public const uint cmdidMyFirstCommand = 0x100;   7:   };   8: }

标识符的名字是我们在项目向导那里填入的,标识符的值0x100是由向导自动生成的。

第二个文件SimpleCommand.vsct似乎更“令人兴奋”,因为它包含XML内容,这更符合我们进行界面定义的习惯。出于可读性的考虑,我去掉了源文件中的注释,该文件内容如下:

1: <?xml version="1.0" encoding="utf-8"?>   2: <CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">   3:   <Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>   4:   <Extern href="vsshlids.h" mce_href="vsshlids.h"/>   5:   <Extern href="msobtnid.h" mce_href="msobtnid.h"/>     6:     7:   <Commands package="guidSimpleCommandPkg">   8:     <Groups>   9:       <Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">  10:         <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>  11:       </Group>  12:     </Groups>    13:     <Buttons>  14:       <Button guid="guidSimpleCommandCmdSet" id="cmdidMyFirstCommand"  15:         priority="0x0100" type="Button">  16:         <Parent guid="guidSimpleCommandCmdSet" id="MyMenuGroup" />  17:         <Icon guid="guidImages" id="bmpPic1" />  18:         <Strings>  19:           <CommandName>cmdidMyFirstCommand</CommandName>  20:           <ButtonText>My First Command</ButtonText>  21:         </Strings>  22:       </Button>  23:     </Buttons>  24:    25:     <Bitmaps>  26:       <Bitmap guid="guidImages" href="Resources\Images_32bit.bmp" mce_href="Resources\Images_32bit.bmp" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>  27:     </Bitmaps>  28:   </Commands>  29:     30:   <Symbols>  31:     <GuidSymbol name="guidSimpleCommandPkg" value="{2291da24-92e5-4ea4-bdb7-72a9b5ac7d59}" />  32:     <GuidSymbol name="guidSimpleCommandCmdSet" value="{a982b107-4ad4-437e-b2bc-cdf2708aa376}">  33:       <IDSymbol name="MyMenuGroup" value="0x1020" />  34:       <IDSymbol name="cmdidMyFirstCommand" value="0x0100" />  35:     </GuidSymbol>  36:     <GuidSymbol name="guidImages" value="{5c3faf04-8190-48c4-a6e9-71f04f1848e5}" >  37:       <IDSymbol name="bmpPic1" value="1" />  38:       <IDSymbol name="bmpPic2" value="2" />  39:       <IDSymbol name="bmpPicSearch" value="3" />  40:       <IDSymbol name="bmpPicX" value="4" />  41:       <IDSymbol name="bmpPicArrows" value="5" />  42:     </GuidSymbol>  43:   </Symbols>  44: </CommandTable>

.vsct文件是Visual Studio 2008 SDK里的一种新的XML格式,vsct代表Visual Studio的命令表(Command Table),Visual Studio利用vsct文件的定义为我们的package的命令创建用户界面。如果你查看这个文件的属性的话,你会发现该文件的Build Action是VSCTCompile。在package编译过程中,vsct文件会被编译成二进制的资源,并以1000作为资源ID添加到VSPackage.resx资源文件中。当regpkg.exe去注册我们的package的时候,vsct文件代表的资源也会注册到Visual Studio中。而当VS实验室启动的时候,VS只需要去读取已注册的资源以便更新VS的界面(例如显示菜单或工具栏项),而不需要加载我们的package。

我认为现在还不是深入研究vsct文件的时候,但是我想告诉你一些重要的东西:通过vsct文件,我们可以学习到VS是怎样架构的,以及它是怎样组织这么多的功能和用户界面的。 - 命令(动作)和触发命令的用户界面是分开的。同一个命令可以被不同的菜单或工具栏调用。 - 多个命令可以分组,利用分组,可以简单的合并到已存在的菜单中。 — 元素是可标识的符号,而不是常量。这样就不容易出错:标识符的名字是唯一的,VSCT编译器会检测输入错误。

它是如何工作的?

现在让我们看看我们的菜单项“My First Command”是怎样显示在Visual Studio中的。我们必须先弄清楚最重要的问题:当我们点击我们的命令对应的菜单项时,Visual Studio是怎样调用相应的动作的?答案就在SimpleCommandPackage.cs文件中,所以让我们来看一下这个文件。

上一篇中,我们看到EmptyPackage类有很多Attribute的标记,regpkg.exe用这些Attribute来注册我们的package。在这一篇里,SimpleCommandPackage类比EmptyPackage多了一个ProvideMenuResourceAttribute:

1: ...   2: [ProvideMenuResource(1000, 1)]    3: ...   4: public sealed class SimpleCommandPackage : Package   5: {   6:   ...   7: }

ProvideMenuResourceAttribute构造函数有两个参数,第一个参数是resourceID,这个参数值必须是1000,因为VSCT编译器在把vsct文件编译到VSPackage资源中的时候,默认用1000作为vsct文件对应的资源ID。第二个参数是versionID,它的取值在资源的缓存机制中非常重要。现在先不要深入研究它的细节了,只需要记住ProvideMenuResourceAttribute允许我们为package注册菜单资源。

为了执行一个命令,我们需要做下面的两步: — 我们需要写一个Command Handler,里面放置命令执行时的逻辑。 — 我们必须以某种方式告诉Visual Studio来调用我们的Command Handler。

在这个例子中,我们的Command Handler将显示一个消息框。Command Handler本身是一个简单的私有方法,包含众所周知的EventHandler的参数。但是,在这个方法体内,却不是仅仅调用Messagebox.Show这么简单:

1: private void MenuItemCallback(object sender, EventArgs e)   2: {   3:   IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));   4:      5:   Guid clsid = Guid.Empty;   6:   int result;   7:     8:   Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(   9:     uiShell.ShowMessageBox(  10:       0,  11:       ref clsid,  12:       "SimpleCommand",  13:       string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()",   14:         this.ToString()),  15:       string.Empty,  16:       0,  17:       OLEMSGBUTTON.OLEMSGBUTTON_OK,  18:       OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,  19:       OLEMSGICON.OLEMSGICON_INFO,  20:       0,        // false  21:       out result)  22:     );  23: }

为了使用Visual Studio提供给Add-in和Package的功能,我们必须使用一些service。在我们这个例子中,我们用到了IVsUIShell这个service,它提供了若干方法去实现与界面有关的功能。为了得到这个service的实例,我们必须调用Package基类的GetService方法,并把SVsUIShell类型作为参数传递给它。

通过这个service的实例,我们可以调用它的ShowMessageBox方法去弹出一个Visual Studio消息框。这一次我不会解释ShowMessageBox方法的参数,你只需要知道它会弹出一个带有“确定”按钮的消息框就行了。(译者注:我们也可以直接用System.Windows.Forms.MessageBox.Show方法来弹出一个消息框,读者可以自己试一下)

Visual Studio的底层用的是COM技术。所有实现的service都可以被COM的interop类访问。如你所知,COM没有提供一个运行时的异常处理机制,它仅仅通过函数的返回值去标识异常。当调用一个COM对象的方法时,我们必须手动的判断返回值,并根据情况抛出异常。Microsoft.VisualStudio.ErrorHandler类的ThrowOnFailure方法帮助我们做了这项工作。

所以,MenuItemCallback方法就是这样执行我们的命令的。不过,我们还得通过某种方式告诉Visual Studio这个方法的存在,这样当用户点击“My First Command”菜单的时候它才会执行我们的命令。秘诀就在Initialize方法中,当Visual Studio加载我们的Package的时候,会调用Package的Initialize方法:

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.guidSimpleCommandCmdSet,   12:       (int)PkgCmdIDList.cmdidMyFirstCommand);  13:     MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );  14:     mcs.AddCommand( menuItem );  15:   }  16: }  17:

我们重写了Initialize方法,在做任何动作之前,先调用基类的Initialize方法。一个命令可以通过很多方式调用:通过主菜单、通过工具栏或者通过上下文菜单。所有的菜单项都是通过命令ID和相应的命令绑定起来的。在这里,我们把命令ID和命令处理方法绑定起来。

通过GetService方法,我们得到了一个OleMenuCommandService的实例,它实现了IMenuCommandService接口,有差不多20个方法和一些属性。我们利用AddCommand方法把了一个Command handler(在这里是MenuItemCallback)和一个命令ID(menuCommandId变量)绑定其来。

当用户点击“My First Command”菜单项时,它是与以GuidList.guidSimpleCommandCmdSetPkgCmdIDList.cmdidMyFirstCommand为标识的命令绑定其来的,而这个命令ID又是和MenuItemCallback关联的,所以会弹出消息框。

总结

我们为package添加了一个简单的菜单命令。为了添加这个命令,我们做了如下的事情:

— 创建了一个vsct文件去描述资源(菜单项、命令和相关的标识符)。这个文件被VSCT编译器编译成二进制的资源,并合并到VSPackage资源中。同时,为了注册这个资源,我们为package添加了一个ProvideMenuResourceAttribute

— 实现了一个方法去弹出消息框。这个方法利用SVsUIShell和Visual Studio交互,以便弹出消息。

— 在package初始化的时候,我们添加了相应代码去把命令和命令处理逻辑绑定在一起。在这里通过OleMenuCommandService和IDE交互,以便将命令和命令处理逻辑关联起来。

下一次,我们将以工具窗口(Tool Window)的形式,为package添加自己的界面。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android app 功能代码覆盖率--Jacoco(一)

    Jacoco覆盖率主要是进行功能测试来统计下所覆盖率的类,方法等,是一种辅助评估项目质量,风险及用例设计是否完善的方法。切记,Jacoco覆盖率并不是指...

    厦门-安仔
  • android UiAutomator长按实现控制按住控件时间的方法

    本人在使用UiAutomator做测试的时候,遇到一些控件需要长按一会儿,比如录音功能,需要按住几秒,官方api不太好用,所以自己写了一个长按的方法。分享如下,...

    八音弦
  • ashx输出文字,执行JavaScript,输出图片等

    一提到Ashx文件,我们就会想到http handler以及图片加载(在之前我们一般使用ASPX或者Webservice去做),一般做法如下:

    javascript.shop
  • 在Android应用中绕过主机验证的小技巧

    android.net.Uri和java.net.URL的解析器中存在问题,它们不识别校验权限部分中的反斜杠(如果你测试java.net.URI将显示异常)。

    用户1467662
  • 移动端吸顶fixbar解决方案

    在IOS端,使用 position: sticky 这个属性,使用类似于 position: relative 和 position: absolute 的...

    Tiffany_c4df
  • 探究Hybrid-APP技术原理

    随着Web技术的发展和移动互联网的发展,Hybrid技术已经成为一种前端开发的主流技术方案。那什么是Hybrid App呢?

    Tiffany_c4df
  • 利用UiAutomator写一个首页刷新的稳定性测试脚本

    本人在做Android APP稳定性测试的过程中,需要测试在不断刷新首页内容的场景下的稳定运行和性能数据的收集。最终根据UiAutomator+多线程解决了这个...

    八音弦
  • css背景图background-position百分比的理解

    <position>值支持1~4个值,可以是具体数值,也可以是百分比,也可以是left, top, right, center, bottom关键字。可参考下图...

    javascript.shop
  • 用Flutter构建漂亮的UI界面 – 基础组件篇

    Flutter作为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差异优势,早已引起大批技术爱好者的关注,甚至一些闲鱼,美团,腾讯等大公司均已开始使用。虽然...

    Javanx
  • 错误操作怎么办?用他让你不再害怕!—Dialog最详解

    Hi,好久不见,甚是想念各位花粉,为了感谢花粉们长久以来的支持,本篇文章继续分享Android中非常实用的干货— Dialog(对话框)!那么什么叫 Dialo...

    下码看花

扫码关注云+社区

领取腾讯云代金券