VisualStudio 扩展开发

栏目: ASP.NET · 发布时间: 5年前

内容简介:本文主要:如何开发一个 visual Studio 扩展,其实扩展也叫插件。 那么就是如何开发一个 vs插件。 本文也记录了我调试 VisualStudio 半个月过程遇到的坑。我写这博客时候,是我在开发一个插件:首先需要安装 Visual Studio SDK ,安装不需要其它的工具就可以,直接使用vs安装包。

本文主要:如何开发一个 visual Studio 扩展,其实扩展也叫插件。 那么就是如何开发一个 vs插件。 本文也记录了我调试 VisualStudio 半个月过程遇到的坑。

我写这博客时候,是我在开发一个插件: 编码规范工具 。记录的是我从不知道到发布插件,如果遇到了开发中的问题,欢迎交流。

安装 Visual Studio SDK

首先需要安装 Visual Studio SDK ,安装不需要其它的 工具 就可以,直接使用vs安装包。

我的是 Visual Studio 2015 ,所以我到这个页面:https://msdn.microsoft.com/en-us/library/bb166441 看教程。

垃圾wr(我说的就是微软),找个东西好难

首先是需要安装 SDK ,如果一开始没有安装的话,那么在控制面板,找到 vS 右击,修改 VS ,然后选择工具安装。

VisualStudio 扩展开发

选择修改,然后选最后一个工具

VisualStudio 扩展开发

我的是中文,可能翻译不一样,不过相信这一点压力对大家没有什么。

然后就来说下如何做插件,主要教程是看: http://dotneteers.net/blogs/divedeeper/archive/2008/01/06/LearnVSXNowPart3.aspx ,除了国外的还有国内的大神翻译:http://www.cnblogs.com/default/archive/2010/02/27/1674832.html 这里是一系列。

那么我将会来说下使用一个简单的方法去做一个 Command ,功能是可以判断 VS 工程的所有文件编码。

首先是新建一个插件项目。打开 vs ,新建一个 VSIXProject

VisualStudio 扩展开发

新建之后居然发现有一个 index.html 我开始还以为是 写html 来着,还好不是,这个 index.html 只是卖萌的而已。

添加菜单

那么新建完 VSIXProject 我们就开始编写按钮,虽然说是按钮,其实是菜单,在这里,全部的按钮都是和菜单一样。

那么我们直接新建 Command ,注意他的位置是在哪。

VisualStudio 扩展开发

新建出来可以看到多了好多文件,其中 .vsct 是核心,如果想知道关于他更多,请去中文博客: http://www.cnblogs.com/default/archive/2010/06/28/1766451.html

我先放出做出了的菜单。

VisualStudio 扩展开发

首先打开 *.vsct 在 Symbols 添加 id ,我们添加 EncodingNormalizerMenu ,EncodingNormalizerId2,他们的值随意给。关于这个 GUID 或者其它的,其实我也不懂。

<GuidSymbol name="guidEncodingNormalizerPackageCmdSet" value="{0640f5ce-e6bc-43ba-b45e-497d70819a20}">
        <IDSymbol name="MyMenuGroup" value="0x1020" />
        <IDSymbol name="EncodingNormalizerId" value="0x0100" />
      
        <!--添加 EncodingNormalizerMenu ,EncodingNormalizerId2-->
        <IDSymbol name="EncodingNormalizerMenu" value="0x1021" />
        <IDSymbol name="EncodingNormalizerId2" value="0x0101" />

    </GuidSymbol>

然后是创建菜单 在 <Command> 下面使用 <Menus>

<Menus>
      <Menu guid="guidEncodingNormalizerPackageCmdSet" id="EncodingNormalizerMenu"
      type="Menu" priority="0x100">
        <!--注意这个id 和 type-->
        <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_BUILDDEBUGRUN" />
        <!--这里的id是说他在哪-->
        <Strings>
          <!--按钮显示的字-->
          <ButtonText>规范编码</ButtonText>
          <!--命令名-->
          <CommandName>EncodingNormalizer</CommandName>
        </Strings>
      </Menu>
    </Menus>

然后修改 Groups ,修改 Parent 的 guid 为 Menu 的,修改 id 为 Menu 的。不一样就不在一个菜单。

<Groups>
      <Group guid="guidEncodingNormalizerPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidEncodingNormalizerPackageCmdSet" id="EncodingNormalizerMenu"/>
      </Group>
    </Groups>

然后添加按钮,注意按钮需要 id 、priority、CommandName 和之前默认的不一样。

添加了按钮,我们需要添加事件,在 .cs 构造 我们使用 CommandID 绑定,我们需要知道 按钮在哪个组,我们直接使用 CommandSet 还需要知道 按钮的 id ,第一个按钮是 0x0100 就是CommandId,第二个是 0x0101 ,于是就写 new CommandID(CommandSet, 0x0101) 使用第二个按钮

那么使用了按钮,我们需要关联事件使用,我的 MenuItemCallback 事件作为按钮点击使用函数。

menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID);
                commandService.AddCommand(menuItem);

代码全部在 全球同性交友平台 ,上面写的代码在 EncodingNormalizerVsx/EncodingNormalizer.cs

接着就是需要加上文件编码检查,在我之前写的 C# 判断文件编码 博客有说道如何检测文件编码。

关于 EncodingNormalizer 为何需要写私有构造以及他的代码为何是需要使用下面的结构,因为这时ms自己加的,我就不去改他了。默认创建的文件就是:

public static void Initialize(Package package)
        {
            Instance = new EncodingNormalizer(package);
        }

        public static EncodingNormalizer Instance { get; private set; }

              private EncodingNormalizer(Package package)
        {
            if (package == null)
            {
                throw new ArgumentNullException("package");
            }

            this.package = package;

            var commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (commandService != null)
            {
               //这里写按钮
                  menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID);
                commandService.AddCommand(menuItem);
            }
        }

文件的其他都是后面加的,所以可以看到,需要添加的按钮写的在构造的哪里,而且打开 xxPagexx 的文件可以发现,这个创建就在 Initialize 使用,也就是下面的代码调用

public sealed class EncodingNormalizerPackage : Package
    {
        /// <summary>
        ///     EncodingNormalizerPackage GUID string.
        /// </summary>
        public const string PackageGuidString = "ffc5dabf-5ded-4433-8225-73b47e154210";

        #region Package Members

        /// <summary>
        ///     Initialization of the package; this method is called right after the package is sited, so this is the place
        ///     where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            EncodingNormalizer.Initialize(this);
            base.Initialize();
        }
    }

所以在开始传入自己,因为传入之后可以通过 ServiceProvider.GetService 获得按钮添加的位置,之后就在他这里添加按钮的对应事件,添加时需要使用和按钮一样的id,这里建议写一个变量。

增加选项

我们需要保存一些设置,那么如何自定义配置的界面,把配置页面放在工具->选项,可以参见 http://www.cnblogs.com/winkingzhang 提供的方法,我使用了他的方法,很简单。还有垃圾wr的方法 https://github.com/Microsoft/VSSDK-Extensibility-Samples/blob/646de671c1a65ca49e9fce397baefe217e9123e8/Options_Page/Readme.md

首先打开 *Package.cs ,不过我们需要新建一个类 DefinitionPage ,不需要在这个类写什么。

public class DefinitionPage
    {
        
    }

然后添加 ProvideProfile ,需要写类型、分类名称、页面名称、资源ID。关于 ProvideProfile 可以去: https://msdn.microsoft.com/zh-cn/library/microsoft.visualstudio.shell.provideprofileattribute.aspx

[ProvideProfile(typeof(DefinitionPage), "PowerExtension", "DefinitionPage",101,104,true)]
  [ProvideOptionPage(typeof(DefinitionPage), "PowerExtension", "DefinitionPage", 101, 104, true)]

PowerExtension 就是显示在选项的一级菜单,DefinitionPage就是二级菜单。

我这里把 PowerExtension 改为 EncodingNormalizer

我们选项不需要复杂的,只需要使用默认的,于是我们添加属性 CriterionEncoding

添加时候,需要让 DefinitionPage 使用 DialogPage

[ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid(GuidStrings.GuidDefinitionPage)]
    public class DefinitionPage : DialogPage

GuidStrings.GuidDefinitionPage) 是我自己定义的GUID放在一起的类,实际没有用

需要让属性知道设置的标题,点击显示的 Description ,于是我就写下面代码

/// <summary>
        /// 规范格式
        /// </summary>
        [Category("规定编码")]
        [Description("规定的编码,如果是ascii 那么无论是选择utf8或GBK都判断为对")]
        public Encoding CriterionEncoding { set; get; }

VisualStudio 扩展开发

如果需要复杂窗体,新建一个 用户控件,注意是 WinForm 控件,然后 override Window。

假如我有一个控件 View.DefinitionPage ,那么我可以使用

protected override IWin32Window Window
        {
            get
            {
                if (_definitionPage == null)
                {
                    _definitionPage = new View.DefinitionPage();
                }
                _definitionPage.Owner = this;
                _definitionPage.InitializeLifetimeService();
                return _definitionPage;
            }
        }
        private View.DefinitionPage _definitionPage;

那么如何读取 vs扩展配置 ?

vsx的配置保存使用上面的方法,就自动保存配置注册表,于是如何去读,就是问题。

读取 vs插件 配置的方法是在 *Package.cs 写静态的 Ensure*Package

public static EncodingNormalizerPackage EnsureEncodingNormalizerPackage()
        {
            IVsShell shell = Package.GetGlobalService(typeof(SVsShell)) as IVsShell;
            if (shell != null)
            {
                IVsPackage package;
                Guid guid = new Guid(EncodingNormalizerPackage.PackageGuidString);
                if (ErrorHandler.Succeeded(shell.IsPackageLoaded(ref guid, out package)))
                {
                    return package as EncodingNormalizerPackage;
                }
                if (ErrorHandler.Succeeded(shell.LoadPackage(ref guid, out package)))
                {
                    return package as EncodingNormalizerPackage;
                }
            }
            return null;
        }

EncodingNormalizerPackage 是我的类,大家替换他为你的类,然后拿到了EncodingNormalizerPackage还需要拿到他的页面属性

DefinitionPage 就是我上面定义的选项

public static DefinitionPage DefinitionPage()
        {
            EncodingNormalizerPackage package = EnsureEncodingNormalizerPackage();
            return package?.GetDialogPage(typeof(DefinitionPage)) as DefinitionPage;
        }

那么在上面定义的 CriterionEncoding 也可以拿到,这样就可以读配置了。

修改 CriterionEncoding 关闭 vs,可以看到下次打开值还在,他是写在注册表。

我发现写注册表对于List的和一些类型都不好,于是我用了写文件,写文件可以写在用户文档。

System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) 获取用户文档,在里面新建文件夹,然后就可以写入读取。

如果需要做方法去和 Resharper 的注册等差不多的页面,那么可以使用 Window 弹出。

首先创建一个 UserControl 然后写一个复杂的界面,假如我写了一个叫 ConformPage 的控件,那么我如何去显示他。可以使用下面代码。

System.Windows.Window window = new System.Windows.Window();
            ConformPage conformPage = new ConformPage();
            window.Content = conformPage;
            window.Title = "编码规范工具";
            window.Show();

从零做一个扩展,可以参见: http://www.cnblogs.com/stg609/p/3711443.html

传到商店

做完了功能,我们需要发布扩展,我们要把按钮加上好看的 ico 。

首先,下载一张图。

把图片改为 32bits 16x16。修改图片可以使用 http://www.easyicon.net/ 在线转换。

图片放到工程,我放在 Resource ,我给他名称 code_711px_16_easyicon.net.png

打开 *.vsct ,在 Symbols 添加 一个GUID。这需要给他名称和ID

<GuidSymbol name="EncodingNormalizerIdICO" value="{B1E160E6-CC24-4FD5-857F-69F45DCEE27B}"
                >
      <IDSymbol name="bmpPic1" value="1" />
    </GuidSymbol>

然后在 Bitmaps 添加 Bitmap ,他的 guid 就是我们上面 EncodingNormalizerIdICO ,href 是图片所在文件夹,usedList 写 IDSymbol 。

<Bitmap guid="EncodingNormalizerIdICO" href="Resources\code_711px_16_easyicon.net.png" usedList="bmpPic1"/>

然后在我们需要的按钮 Icon 的 guid 写 guid,在 id 写 IDSymbol 。

这样就好啦。

如果发现有自己做的和我讲的不同,那么一定是有一点我没说道,去我的github看我做的。

图片可以把多个图片放在一起,于是按照一个图片 16x16 读取,这就是多个 IDSymbol 做的。

获取工程所有项目

我需要获取用户工程的所有项目,我开始使用 dte.Solution.Projects 但是放在文件夹的项目获取不到,所以使用堆栈提供的方法。

这个方法写在 C# 解析 sln 文件 可是 vs 说找到不 Microsoft.Build.dll 所以这个方法还是不可以的。那么如何从 dte 获取所有项目?我找到一个大神博客: http://www.wwwlicious.com/2011/03/29/envdte-getting-all-projects-html/

开始判断是不是文件夹,如果是的话,递归函数获取文件夹所有项目。

我以为文件夹不是 Project 但是后来发现,工程的文件夹也是 Project 文件夹可以通过 ProjectKinds.vsProjectKindSolutionFolder 判断。

那么如何获得 文件夹所有文件夹和项目,其实 Project 有 ProjectItems 可以获取。

于是可以使用这个方法

private static List<Project> GetSolutionFolderProjects(Project solutionFolder)
        {
            List<Project> list = new List<Project>();
            for (var i = 1; i <= solutionFolder.ProjectItems.Count; i++)
            {
                var subProject = solutionFolder.ProjectItems.Item(i).SubProject;
                if (subProject == null)
                {
                    continue;
                }

                // If this is another solution folder, do a recursive call, otherwise add
                if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    list.AddRange(GetSolutionFolderProjects(subProject));
                }
                else
                {
                    list.Add(subProject);
                }
            }

            return list;
        }

一开始判断是不是文件夹,如果是就使用 GetSolutionFolderProjects 得到所有的项目,这样就可以获得工程所有项目。

foreach (var temp in dte.Solution.Projects)
  {
    if (temp is Project)
    {
        if (((Project) temp).Kind == ProjectKinds.vsProjectKindSolutionFolder)
        {
           project.AddRange( GetSolutionFolderProjects((Project) temp));
        }
        else
        {
           project.Add((Project)temp);
        }
    }

  }

代码: https://gist.github.com/lindexi/3105bd0f0c5225bec4aa476f84dd29db

升级 2017

如果有之前扩展需要升级,参见 https://docs.microsoft.com/en-us/visualstudio/extensibility/how-to-migrate-extensibility-projects-to-visual-studio-2017

  1. 安装 vs2017 需要添加扩展

关于vs2017 可以到我网盘下载,参见: http://lindexi.oschina.io/lindexi/post/C-7.0/

  1. 打开 Nuget 升级,把所有提示升级的都升级。

VisualStudio 扩展开发

  1. 打开 source.extension.vsixmanifest

选 InstallationTarget 包括各版本

VisualStudio 扩展开发

VisualStudio 扩展开发

  1. 打开属性,修改路径

启动外部程序 C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\devenv.exe

VisualStudio 扩展开发

  1. 启动项目

  2. 把插件共享 https://visualstudiogallery.msdn.microsoft.com/site/upload/view

参见: http://blog.csdn.net/liuruxin/article/details/17955363

https://github.com/Microsoft/VSSDK-Extensibility-Samples

https://msdn.microsoft.com/zh-cn/library/cc138589

https://msdn.microsoft.com/zh-cn/library/dn705845

https://msdn.microsoft.com/zh-cn/library/mt683786

代码: https://github.com/lindexi/EncodingNormalior

如果开发中遇到问题,欢迎联系 lindexi_gd@163.com

现在我还有另一个插件: 图片注释 这个插件不是我写的,我是修改的,所以没有发布,如果需要就在这里下。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

python学习手册(原书第5版)

python学习手册(原书第5版)

马克·卢茨 / 机械工业出版社 / 2018-10 / 219

如果你想动手编写高效、高质量并且很容易与其他语言和工具集成的代码,本书将快速地帮助你利用Python提高效率。本书基于Python专家的流程培训课程编写,内容通俗易懂。本书包含很多注释的例子和插图,以帮助你开始使用Python2.7和3.3。每章都包含关于Python语言的重要组成部分的一节课。本书主要内容:了解Python的主要内置对象类型,如数字、列表和字典;创建和处理对象的Python语句,......一起来看看 《python学习手册(原书第5版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具