本文主要:如何开发一个 visual Studio 扩展,其实扩展也叫插件。

那么就是如何开发一个 vs插件。

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

安装 Visual Studio SDK

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

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

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

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

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

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

然后就来说下如何做插件,主要教程是看: 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

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

添加菜单

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

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

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

我先放出做出了的菜单。

首先打开 *.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# 判断文件编码 博客有说道如何检测文件编码。

增加选项

我们需要保存一些设置,那么如何自定义配置的界面,把配置页面放在工具->选项,可以参见 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; }

如果需要复杂窗体,新建一个 用户控件,注意是 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/

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

  3. 打开 source.extension.vsixmanifest

    选 InstallationTarget 包括各版本

  4. 打开属性,修改路径

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

  5. 启动项目

  6. 把插件共享 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

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


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

VisualStudio 扩展开发的更多相关文章

  1. VisualStudio 扩展开发 获得输出窗口内容

    本文告诉大家如何拿到 VisualStudio 输出窗口的内容 在上一篇告诉大家如何开发添加菜单 点击的时候可以使用方法,如果需要拿到 VisualStudio 的输出窗口的内容,如想要开发一个插件, ...

  2. 利用Visual Studio 2017的扩展开发(VSIX、ItemTemplate) 快速实现项目的半自动化搭建

    目录 0.引言 1.什么是Visual Studio项目模板 2.IWizad接口 3.通过Visual Studio扩展开发实现领域驱动开发 3.1 使用VSIX+ProjectTemplate创建 ...

  3. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

  4. PHP 扩展开发(将自己的一些代码封装成PHP扩展函数)

    今天时间不多,先给个地址,能搜到我这篇blog的朋友先看看我最近在看的一些文章.资料吧: 我的环境是 lnmp1.1 的 (LNMP一键安装包),所以要进行PHP扩展开发首先应该对环境配置和shell ...

  5. 关于PHP扩展开发(收藏)

    一.Linux shell命令: ls –lh    查看文件大小 du –a    查看文件及文件夹大小 -------------------------- nginx ------------- ...

  6. postgres扩展开发

    扩展开发的基本组成 demo--1.0.sql demo.c demo.control Makefile demo.c当中包含了自定义函数的实现,纯C语言,目录下可包含多个.c文件.demo-1.0. ...

  7. 【转发】NPAPI学习(Firefox和Chrome扩展开发 )

    NPAPI学习(Firefox和Chrome扩展开发 ) 2011-11-08 14:41:02 by [6yang], 1172 visits, 收藏 | 返回 Firefox和Chrome扩展开发 ...

  8. Chrome扩展开发之二——Chrome扩展中脚本的运行机制和通信方式

    目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...

  9. PHP扩展开发相关总结

    1.线程安全宏定义 在TSRM/TSRM.h文件中有如下定义 #define TSRMLS_FETCH() void ***tsrm_ls = (void ***) ts_resource_ex(0, ...

随机推荐

  1. Swing-setMaximumSize()用法-入门

    与setMinimumSize()一同讨论.顾名思义,这两个函数用于设置窗体的最大.最小值.然而测试发现,setMaximumSize()直接作用于JFrame时,无法实现其预定功能,setMinim ...

  2. 201521123099 《Java程序设计》第八周学习总结

    1. 本周学习总结 2. 书面作业 本次作业题集集合 List中指定元素的删除(题目4-1) 1.1 实验总结 老师上课解释的还有私下问同学大概能懂.主要理解到一点在删除List中元素时要考虑元素删除 ...

  3. 201521123095《java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结上课内容. 对于一个系统中,对于名词大多为类或属性,对于动词大多为方法. 1.3 注释的应用 使用类的注释与 ...

  4. 201521123113 《Java程序设计》第2周学习总结

    1.本周学习总结 学习了各种java数据类型以及各种运算符的使用 string类之所以好用是因为这是人可以看得懂的类型,操作简便 Scanner扫描器与标准输出输入用法上的不同,Scanner较标准输 ...

  5. 201521123106 《Java程序设计》第12周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对象(属性:int id, String name,int age,doubl ...

  6. PowerBI开发 第四篇:DAX表达式

    DAX 表达式主要用于创建度量列(Measure),度量值是根据用户选择的Filter和公式,计算聚合值,DAX表达式基本上都是引用对应的函数,函数的执行有表级(Table-Level)上下文和行级( ...

  7. angular 时间戳转换

    .filter('getWeek', function() { return function(input) { var date = new Date(input * 1000); var week ...

  8. MongoDB中的映射,限制记录和记录拼排序 文档的插入查询更新删除操作

    映射 在 MongoDB 中,映射(Projection)指的是只选择文档中的必要数据,而非全部数据.如果文档有 5 个字段,而你只需要显示 3 个,则只需选择 3 个字段即可. find() 方法 ...

  9. servlet_2

    package com.atguigu.servlet; import java.io.IOException; import javax.servlet.Servlet;import javax.s ...

  10. 实现一个简单的虚拟DOM

    现在的流行框架,无论React还是Vue,都采用虚拟DOM. 好处就是,当我们数据变化时,无需像Backbone那样整体重新渲染,而是局部刷新变化部分,如下组件模版: <ul class=&qu ...