• VS 2003 IDE插件开发指南


    作者 sema

    2005-04-15 10:03

    Visual Studio.NET插件能做很多事情,例如:
    1、 编写如CodeRush一样的开发环境代码辅助工具
    2、 编写如CodeSmith这样的代码模板工具
    3、 编写代码生成器,根据自定义的一些条件自动生成代码。如现在比较流行的一些代码生成工具,如果和开发环境集成,使用起来应该会更加方便。
    4、 编写如DataSetPryer这样的调试工具,可以在调试时查看DataSet的内容。
    5、 甚至还可以在VS.NET里集成Google搜索引擎,或将MSN集成到VS.NET。
    这里不再一一列举,总而言之,凡是可以和Visual Studio.NET开发环境相关的,都能以插件的形式进行。
    开发VS.NET插件,目前有两种形式:一是利用VS向导生成的VS外接程序;二是利用微软的VSIP开发包(Visual Studio Industry Partner:微软合作伙伴计划)。本文讨论的是第一种方法。

    二、 程序框架概述
    在Visual Studio.NET中选择”新建项目à其他项目à扩展性项目àVisual Studio.NET外接程序”,按照向导生成代码,最后会生成两个工程文件,一个是外接程序项目,一个是外接程序安装项目。可以在外接程序项目里看到生成的项目文件中有个connect.cs文件,该文件有以下几个部分:

    1 类的继承接口及其常量定义

    [GuidAttribute("952A6CFF-8516-4DA0-B0BA-519CB9614525"), ProgId("STDTools.Connect")]
    public class Connect : Object, Extensibility.IDTExtensibility2, IDTCommandTarget
    {…}
    Connect类主要从两个接口继承,一个是Extensibility.IDTExtensibility2接口,该接口主要定义了下面几个方法:
    OnAddInsUpdate 方法:在环境中加载或卸载外接程序时发生。
    OnBeginShutdown 方法:正在关闭环境时发生。
    OnConnection 方法:将外接程序加载到环境中时发生。
    OnDisconnection 方法:当从环境中卸载外接程序时发生。
    OnStartupComplete 方法:环境启动完毕时发生。

    IDTCommandTarget接口则定义了以下两个方法
    Exec 方法:在VS开发环境中选择了某个外接菜单命令时被VS环境所调用。
    QueryStatus方法:当VS环境要显示外接菜单时调用该方法查询菜单的状态。
    该方法返回指定的已命名命令的当前状态,无论此命令是启用、禁用还是隐藏

    2   OnConnection()函数:
    本事件处理函数是在插件被加载时发生,一般用于做一些初始化工作,如创建菜单等。该函数的传入参数如下:
    object application:定义了IDE自动化对象
    Extensibility.ext_ConnectMode connectMode:连接模式,指明了插件当前的连接模式
    ext_cm_AfterStartup 外接程序是在应用程序启动后加载的,或是通过将相应 AddIn 对象的 Connect 属性设置为 True 加载的。
    ext_cm_Startup 外接程序是在启动时加载的。
    ext_cm_UISetup 外接程序自安装后首次被启动。

    3   OnDisconnection()函数:系统卸载插件时被调用
    本事件处理函数是在插件被卸载时发生,其传入参数如下
    Extensibility.ext_DisconnectMode disconnectMode:
    ext_dm_HostShutdown:外接程序是在开发环境关闭时卸载的。
    ext_dm_UserClosed:外接程序是在用户清除“外接程序管理器”对话框中该外接程序的复选框时卸载的
    ext_dm_UISetupComplete:外接程序是在环境安装完成后和在 OnConnection 方法返回后卸载的。

    4   QueryStatus()函数:
    系统查询菜单状态

    5   Exec()函数:
    在VS开发环境中选择了某个外接菜单命令时被VS环境所调用,在这里可以编写自己的响应代码,例如运行自己的程序或弹出某个窗口。

    三、 处理菜单
    在OnConnect方法中可以进行一系列初始化工作,其中之一就是生成菜单

    1 添加菜单条菜单和工具条菜单

    applicationObject = (_DTE)application;
    addInInstance = (AddIn)addInInst;
    if(connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup
    || connectMode == Extensibility.ext_ConnectMode.ext_cm_Startup)
    {//    如果是安装状态或是插件刚被启动的状态,则创建菜单
    object []contextGUIDS = new object[] { };

    //获取IDE环境的Command集合和CommandBar集合 Commands commands = applicationObject.Commands;
    _CommandBars commandBars = applicationObject.CommandBars;

    try
    {
    //菜单条对象和工具条对象都是CommandBar类型
    CommandBar menuObj,toolbarObj;
    //生成新的子菜单对象,将会被插入到菜单条和工具条对象上
    Command commandObj = commands.AddNamedCommand(addInInstance,
    "PublishUserManage",
    "添加用户管理代码",
    "添加用户管理的代码",
    true,
    127,
    ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled
    );
    #region
    这里有几点要说明:
         1、菜单图片:
    以上的参数中的true,bool MSOButton参数为true,意味着该菜单命令的图片是来源于Office资源库,则后面的int Bitmap(位图ID号)则指明了是哪个图片。如何知道哪个ID号对应哪个图片?可以从微软的页面上下载一个用VBA写的工具,该工具列举出了所有的图片资源(附录工具中已经包含FaceID.xls)。
    以上的参数中的true(bool MSOButton),意味着该菜单命令的图片是来源于Office资源库,则后面的127(int Bitmap--位图ID号)则指明了是哪个图片。如何知道哪个ID号对应哪个图片?可以从微软的页面上下载一个用VBA写的工具,该工具列举出了所有的图片资源(附录工具中已经包含FaceID.xls)。
    如果不想用Microsoft Office自带的图片资源,则需要先在自己的解决方案中添加一个MFC DLL的工程,然后在其中加入图片资源;然后将MSOButton设为False,将Bitmap位图ID设为该资源文件中图片资源的ID号,然后还要在安装程序里添加一个文件夹,同时添加注册表项。具体过程就不再多说了,可以看参考资料
    2、菜单项的名称
    函数的第二项参数string Name(本例中是”PublishUserManage”),系统在生成菜单对象时会自动在Name前面添加本插件的ProgID代表的字符串作为菜单对象的全名。后面在根据名称检索该菜单对象时,需要用全名,本例中应该是”STDTools.Connect.PublishUserManage”;
    */
    #endregion
    //添加主菜单
    CommandBarButton buttonObj;
    //创建主菜单项和工具条
    menuObj = (CommandBar) applicationObject.Commands.AddCommandBar("代码生成(&C)" ,
      vsCommandBarType.vsCommandBarTypeMenu,
      applicationObject.CommandBars["MenuBar"],10);
    toolbarObj = (CommandBar) applicationObject.Commands.AddCommandBar(
                    "CodeTools(&C)",
    vsCommandBarType.vsCommandBarTypeToolbar,
    null,
    -1);
    toolbarObj.Position = Microsoft.Office.Core.MsoBarPosition.msoBarTop;
    //增加子菜单
    //将子菜单加入主菜单和工具条
    buttonObj = (CommandBarButton) commandObj.AddControl(menuObj, menuObj.Controls.Count + 1);
    buttonObj = (CommandBarButton) commandObj.AddControl(toolbarObj, toolbarObj.Controls.Count + 1);
    buttonObj.Style = MsoButtonStyle.msoButtonIcon;
    //将子菜单加入Project的右键菜单
    CommandBar projBar = this.applicationObject.CommandBars["Project"];
    commandObj.AddControl(projBar,1);
    }
    catch(System.Exception ex)
    {
    string error = ex.Message;
    }

    2 添加右键弹出快捷菜单

    除了上面在VS开发环境中添加常规菜单外,还允许用户为开发环境添加一些右键弹出菜单项。下面代码是为代码编辑窗口添加右键弹出菜单。
                //检索代码编辑窗口右键弹出菜单的工具条
    CommandBar projBar = this.applicationObject.CommandBars["Code Window"];
    //将自己的菜单项加入工具条
                commandObj.AddControl(projBar,1);
    如果想在鼠标右键点击“解决方案资源管理器”中的某项目结点时弹出的菜单条中添加自己的菜单项,则只需把上面的"Code Window"改成"Project"即可。如何知道是"Code Window"或"Project"这个没有什么资料说明,但是也很简单,只要编写一个插件程序,列举出所有的菜单条对象就行了。幸好笔者已经做了这件事情,把所有的菜单条对象的名称都列在附录的CommandBar_Names.txt文件里了,你所要做的就是根据名称去猜哪个是你所需要的菜单条了。

    3 卸载菜单

    为什么需要卸载菜单?
    因为如果在你的插件中没有处理卸载菜单,则用户通过“工具à外接程序管理器”暂时关闭了你的插件,或者是通过添加/删除程序卸载了你的插件时,菜单项依然存在,但是却已经不能执行命令,这是不合理的。
    卸载菜单是在OnDisconnection方法中实现的,具体可参见下载资源里的STDTool里的Connect.cs文件中的OnDisconnection方法。在该方法中的代码比较简单,先判断是不是VS环境正在关闭或用户通过外接程序暂停了插件,然后查找到子菜单项并删除,再查找到主菜单项和工具条项,从系统菜单集合里移除掉这些命令条对象。

    4 确定菜单状态

    菜单显示时有一项比较重要的工作,就是根据应用环境的不同,菜单对象的显示状态也在不断变化。也即可用,禁用,不显示。每当VS集成环境要显示一个插件的菜单时,它会调用QueryStatus()方法查询该菜单应该显示的状态。具体代码可参见下载资源里的STDTool里的Connect.cs文件中的QueryStatus方法,该方法先预设菜单项状态为受支持状态(vsCommandStatusSupported,如果不受支持,则菜单不会显示),然后判断条件满足的情况下添加菜单为可用状态(vsCommandStatusEnabled),如果不满足条件,则菜单为禁用状态。

    5 执行菜单命令

    当用户选择插件的菜单项时,集成环境会调用Exec()方法,具体代码可参见下载资源里的STDTool里的Connect.cs文件中的Exec方法,该方法查询被执行的菜单的名称是本插件所期望的名称,则执行一段代码,如显示窗口等。执行成功,则设置handled为true,告诉集成环境已经成功执行命令。

    四、 使用窗口

    1 使用WinForm窗口

    在VS.NET中显示WinForm窗口非常容易,和平常编写WinForm程序没什么两样,步骤如下:
    l          在项目里添加一个WinForm窗体
    l          修改WinForm窗体的构造函数
    将VS环境的根对象DTE对象传入,以便在WinForm中可以操纵开发环境
              public class MyForm : System.Windows.Forms.Form
              {       
    private EnvDTE.DTE applicationObject;

             public MyForm(EnvDTE.DTE applicationObject)
             {
                  InitializeComponent();
                  this.applicationObject = applicationObject;
                  …
              }

    l          在Connect文件的Exec函数里生成并显示WinForm窗体。
                  MyForm form = new MyForm(this.applicationObject.DTE);
                  form.ShowDialog();
    注意:这里的this.applicationObject是_DTE接口指针,它的一个成员DTE成员才是DTE对象,不能直接用类型转换如(DTE) this.applicationObject

    2 使用工具窗口

    VS.NET的工具窗口指的是类似“解决方案资源管理器”,“类视图”,“工具箱”这样停靠在上下左右侧边的窗口。而如代码编辑窗口这样的窗口,需要用VSIP进行开发,外接程序插件是没有办法开发这类窗口的。
    根据MSDN里的资料,如果要开发侧边工具窗口,是件非常繁琐的事情。因为VS.NET是基于COM开发的,所以开发出来的侧边工具窗口也需要实现一系列接口,也就是说,不能直接使用.NET程序集。幸而已经有人做了这件事情
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/vstchFAQAboutVSNETAutomation.asp),
    开发了一个将.NET用户控件包装成COM组件的容器VSUserControlHostLib,使得我们只需要开发出.NET用户控件,就可以将其嵌入到VS集成环境里了
    开发步骤:
    1、 生成Addin项目
    2、 在项目中添加VSUserControlHost.dll引用
    3、 在Connect.cs文件的OnConnection方法中添加显示窗口的代码
    下面是代码示例:(添加在OnConnect方法中)
    //GUID串可以用VS开发环境里菜单命令工具à创建GUID”弹出的对话框创建
             string guidstr = "{65FADCBF-63D2-448b-8A4B-393D7E751345}";
             object objTemp = null;
             VSUserControlHostLib.IVSUserControlHostCtl objControl = null;
             //这里先生成容器窗口,返回的控件对象在objTemp
             windowExplorer = applicationObject.Windows.CreateToolWindow(
    addInInstance,
    "VSUserControlHost.VSUserControlHostCtl",
    "菜单浏览器",
    guidstr,
    ref objTemp);
             //使用容器窗口时,必须在调用该容器控件前设置其为可见,否者将会不能正常显示
             windowExplorer.Visible = true;
             windowExplorer.IsFloating = false;
             //将返回的工具窗口转换为容器对象
             objControl = (VSUserControlHostLib.IVSUserControlHostCtl)objTemp;
             System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
             //用容器对象包含用户控件,
             menuExplorer = (MenuExplore)objControl.HostUserControl(asm.Location,
    "MenuBrowser.MenuExplore");
             //设置窗口的图标
    //windowExplorer.SetTabPicture(…)
                  //DTE对象赋给窗口
    menuExplorer.Application = applicationObject;

    说明:
    l          生成工具窗口:
    以上代码先用CreateToolWindow函数生成工具窗口,工具窗口里承载的VSUserControlHost.VSUserControlHostCtl类型控件由objTemp对象返回
    l          设置可见性:
    将工具窗口设置为可见,非浮动
    l          获取IVSUserControlHostCtl接口:
    将返回的工具窗口中承载的控件对象objTemp转换为IVSUserControlHostCtl接口类型,并赋给objControl对象
    l          加载用户控件:
    用objControl的HostUserControl方法加载指定程序集的指定用户控件对象,上面代码中因为MenuBrowser.MenuExplore类是在插件项目的程序集里,所以用System.Reflection.Assembly.GetExecutingAssembly()方法获取当前程序集位置。如果用户控件是在其他程序集里,需要根据实际情况指定程序集位置

    4、 最后还应该在OnDisconnection方法里关闭窗口
    if(windowExplorer!= null)
    {
         windowExplorer.Visible = false;
    }

    五、 操纵VS开发环境

    VS.NET自动化模型涉及的面太广,本文只针对一些专题加以说明。
    1、 利用代码模型浏览代码
    l          获取代码模型对象
    //获取当前正活动的文档
         Document activeDocument = applicationObject.ActiveDocument;
    //获取代码模型对象,
    //请自行判断activeDocument,ProjectItem,FileCodeModel以及CodeElements是否不为空
    CodeElements codeElements = activeDocument.ProjectItem.FileCodeModel.CodeElements;
    //检索代码元素
         foreach(CodeElement ce in codeElements)
         {
             result.AddRange(getCodeElements(ce));
         }
    说明:
    上面的代码先从DTE对象(也即VS自动模型的根对象)获得当前正活动的文档对象
    然后再根据文档对象获取和它关联的项目元素,这里的ProjectItem其实就是在解决方案资源管理器下面的项目文件夹对应的子项。可以是各种类型,如文件,文件夹等
    再从项目元素获得关联的文件代码模型
    根据文件代码模型获取其代码元素的集合CodeElementCollection,代码元素的父类为CodeElement,下面派生出许多的子类,代表具体的代码元素,分别有:
    CodeNamespace、CodeClass、CodeInterface、CodeFunction
    CodeProperty、CodeVariable、CodeDelegate、CodeStruct、CodeEnum
    代表着命名空间、类、接口、函数、属性、变量、委托、结构、枚举等类型的代码元素,某个CodeElement具体是何类型,可以根据CodeElement. vsCMElement枚举类型来判断,然后再显式转换成具体的子类即可
    但是更进一步的代码元素,如函数体内的语句,好像还没有看到有方法检索。不过也可以利用一些其它的方法,比如CodeRush的程序集里面提供了一个StructuralParser程序集,用于C#的代码分析。如果谁有兴趣可以研究研究

    l          根据具体类型检索代码元素
    具体可参见下载资源包里的MenuBrowser的getCodeElements方法,这里不多作解释
    2、 操纵解决方案及其项目文档
    l          获取当前被选中的项目
    下面代码演示了鼠标点击一个项目,或项目中的子项时,如何得到该项目对象
    public Project GetSelectedProject()
    {
         Project project = null;
         //从被选中对象中获取工程对象
         EnvDTE.SelectedItem item = Application.SelectedItems.Item(1);
         if(item.Project != null)
         {//被选中的就是项目本生
             project = item.Project;
         }
         else
         {//被选中的是项目下的子项
             project = item.ProjectItem.ProjectItems.ContainingProject;
         }
         return project;
    }
    l          获取当前项目的所在目录
    private string GetSelectedProjectPath()
    {
             string path = "";
         //获取被选中的工程
             Project project = GetSelectedProject();
             if(project != null)
             {
             //全名包括*.csproj这样的文件命
                       path = project.FullName;
             }
         //去掉工程的文件名
             path = Path.GetDirectoryName(path);
             return path;
    }
    l          将文件加入工程中
    //获取被选中的工程
    Project project = this.GetSelectedProject();
    //将文件夹下的文件加入工程
    project.ProjectItems.AddFromDirectory(sdir);
    //将单个文件加入工程
    project.ProjectItems.AddFromFile(nfile);
    l          向项目中加入程序集引用
    using VSLangProj;
    private void AddReference(string assembly)
    {
             Project project = GetSelectedProject();
             VSProject vsproject = null;
             if (project.Kind == PrjKind.prjKindCSharpProject)
             {
             //工程类型为C#工程,projectObject成员转换为VSProject对象
                       vsproject = project.Object as VSProject;
             }
             if(vsproject != null)
             {
              //获取C#工程的引用集
                       VSLangProj.References refers = vsproject.References;
                       if(refers != null)
                       {
                  //将程序集引用添加到工程中
                                refers.Add(assembly);
                       }
             }
    }
    3、 在代码编辑窗口操纵代码
    这里不再详述,只是列出网上的资源,大家可以自行参考

    六、 参考资料
    http://www.knowdotnet.com/add-insmacros.html
    msdn帮助目录:Visual Studio .NET-->使用 Visual Studio .NET 进行开发-->参考-->自动化与扩展性参考-->公共环境对象模型

  • 相关阅读:
    xxx
    部署在自己的加了分享,试下
    疑问
    去掉分享
    womenzijide_jiafenxiang
    womenzijide2
    womenzijide
    xiugai-去除js注释
    xiugai2
    《设计模式之禅》读书笔记(一)——单例模式
  • 原文地址:https://www.cnblogs.com/zqonline/p/1317062.html
Copyright © 2020-2023  润新知