Visual Studio 的加载项






4.83/5 (12投票s)
一篇关于如何为 Visual Studio 构建插件的文章。
引言
Visual Studio 万岁!我们都知道它作为一个开发环境来说非常出色——好吧,有些人可能更喜欢 Visual Studio 6,还有些人可能更喜欢 Textpad;但总的来说,它还是相当不错的。
不过,它并不完美。我们可能都见过 Resharper 等时髦的插件,但你知道吗,创建你自己的插件来做你想要的,其实非常简单!
在本例中,我们将从头开始创建一个插件,并在此过程中解释一些选项。我们将看到如何创建一个可以执行一组任意功能的插件。我们将实现什么呢?尽管我有两台巨大的显示器,但我仍然对过长的行感到恼火(我知道这很悲哀,但就是这样),所以我们将添加功能,在编译时标记过长的行。与此同时,我们还将为每个没有版权声明的文件顶部添加一个 © 声明,将项目的警告级别设置为 4,将警告视为错误,并可能完成 assembly.info 文件中的一些信息。这应该足以让我们继续下去,并让我们对系统有一个很好的了解。
好的,那就开始吧!启动 Visual Studio(本示例是使用 VS.NET 2003 完成的,但应该也兼容 2005)。
我们需要一个新的项目,尽管它是在 C# 中,但它没有列在“C# 项目”下,你需要查看“其他项目”,我们想要的是一个“Visual Studio .NET 插件”类型的“可扩展性项目”。
我们将插件命名为:MyFirstAddin。
插件向导
你现在会看到一个向导屏幕
点击下一步…
当然,我们想用 C# 来做,所以下一步…
我们的插件目标是 Visual Studio .NET 和 VSMacros IDE,所以我们可以清除那个复选框。下一步!
在此对话框中,我们需要为插件提供一个名称和描述。这两个都是可选的,但它们用于向用户标识插件——所以我们应该给它一个合理的名称。为了保持一致性,我们称之为“MyFirstAddin”。
下一步!
好的,向导快完成了。虽然我们确实希望这个插件“自动”运行,但我们也会给用户从菜单栏运行它的选项,所以勾选第一个框。
我们可能会显示模式消息框,所以我们将其留空。
是的,我们希望插件在 VS.NET 启动时加载,而且,既然我们不贪心,我们将在机器上与每个人共享它。
下一步!
我们这里不需要“关于”框,所以也可以跳过。
进入代码。
安装项目
你将看到为你创建了一个解决方案。第一个是插件本身的代码,第二个是安装插件并包含你选择的选项的安装项目。我们现在来简单看一下。
我在这里假设你熟悉安装项目,所以我只会关注这里相关且有趣的选项。
查看安装项目的注册表,你会注意到密钥安装在 HKLM/Software/Microsoft/VisualStudio/7.1/Addins 中。如果我们在安装时选择将插件限制给安装它的人,则该密钥将位于 HKCU 下。
查看子键,它的名称是我们之前在安装向导中给出的名称,而 .Connect
指的是插件中调用的函数。是的——MyFirstAddin.Connect
是一个 COM 对象的 ProgID!
其中有一些键。Description
和 FriendlyName
不需要解释,但其他的可能需要。
CommandLineSafe
- 0 或 1。一个布尔值,指示插件是否可以安全地从命令行运行;如果它显示模式对话框,则不能。
CommandPreLoad
- 这用于指示插件是否通过工具栏进行通信。布尔值。
LoadBehaviour
。一个DWORD
位字段。- 0 - 插件未加载
- 1 - 插件在 IDE 启动时加载
- 4 - 当从命令行使用 build 开关调用 devenv 时加载插件。
更多信息请参见 MSDN。
插件
插件功能的代码位于 connect.cs 中。当插件加载时(在我们的示例中,是在 VS.NET 启动时;在其他示例中,可能是在点击工具栏按钮时),Connect
类被实例化并调用 OnConnection
方法。我们将在那里完成我们的工作。
鉴于我们正在修改项目,可能需要通过三种方式进行
- 项目构建时
- 文件保存时
- 用户发起命令时
对于前两种情况,我们需要分配相应的委托。
EnvDTE
命名空间提供了一些类,我们这里将使用的类是 BuildEvents
和 DocumentEvents
。
我们需要两个私有成员变量
public class Connect : Object,
Extensibility.IDTExtensibility2,IDTCommandTarget
{
…
BuildEvents m_BuildEvents;
DocumentEvents m_DocumentEvents;
…
我们将在 OnConnect
方法中分配这些成员。
public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;
…
m_DocumentEvents = applicationObject.Events.get_DocumentEvents(null);
m_BuildEvents = applicationObject.Events.BuildEvents;
…
然后使用 Intellisense 生成委托
m_DocumentEvents.DocumentSaved +=new
_dispDocumentEvents_DocumentSavedEventHandler(m_DocumentEvents_DocumentSaved);
m_BuildEvents.OnBuildBegin += new
_dispBuildEvents_OnBuildBeginEventHandler(m_BuildEvents_OnBuildBegin);
这将为我们创建两个空函数(m_DocumentEvents_DocumentSaved
和 m_BuildEvents_OnBuildBegin
),我们可以在其中放置我们的检查代码。
趁着我们在 OnConnection
中,何不为右键单击弹出的菜单添加一个菜单选项呢。
if (connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup)
{
Commands commands = applicationObject.Commands;
_CommandBars commandBars = applicationObject.CommandBars;
object [] contextGUIDS = new object[] { };
try
{
Command command = commands.AddNamedCommand(addInInstance,
"CheckProject",
"CheckProject",
"Checks the project for conformity",
true,
162,
ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported +
(int)vsCommandStatus.vsCommandStatusEnabled);
CommandBar commandBar = commandBars["Project"] as CommandBar;
CommandBarControl commandBarControl = command.AddControl(commandBar,1);
}
catch (Exception e)
{
String strDebug = e.Message;//to give us something to look at if debugging
}
}
此代码检查 connectMode
(如下所述),如果是在插件设置期间,则将命令添加到项目菜单中。
连接模式定义
ConnectMode | 描述 |
---|---|
ext_cm_AfterStartup |
插件在 IDE 启动后加载,可能是从插件管理器加载的。 |
ext_cm_Startup |
插件在 IDE 启动时加载。 |
ext_cm_External |
插件由 IDE 以外的程序加载 |
ext_cm_CommandLine |
devenv 命令加载了插件。 |
ext_cm_Solution |
当加载对插件有依赖关系的项目时加载插件。 |
ext_cm_UISetup |
插件首次加载。 |
现在将有三个地方会调用插件中的代码:上面定义的两个生成的委托,以及从菜单栏调用时的 Exec
方法。
为了简化我们的生活,我们将在每个方法中放置相同的函数调用,尽管在调用 OnSaved
委托时,这可能有点过头了,因为你可能只需要检查单个文件。
现在,我们将查看这三种方法中的每一种。我们将从 DoCheck()
方法开始。
此函数的主要部分是循环遍历解决方案中的项目并检查它们是否符合规范。这可以通过以下代码片段完成
System.Array solutionProjects =
applicationObject.DTE.ActiveSolutionProjects as System.Array;
foreach (Project project in solutionProjects)
{
CheckProject(project);
}
这让我们进入了 CheckProject
的实现
设置警告级别等很容易
foreach (Configuration configuration in project.ConfigurationManager)
{
foreach (Property property in configuration.Properties)
{
if (property.Name == "TreatWarningsAsErrors")
{
property.Value = true;
}
if (property.Name == "WarningLevel")
{
property.Value = 4;
}
}
}
接下来,我们将填写解决方案的程序集信息。这稍微复杂一些,所以我们将它拆分到一个单独的函数中
foreach (ProjectItem projectItem in project.ProjectItems)
{
string strFileName = projectItem.Name.ToUpper();
if ((strFileName == "ASSEMBLYINFO.CS") ||
(strFileName == "ASSEMBLYINFO.CPP"))
{
if (!FileContainsString(projectItem, s_strCopyright, true))
{
FillInAssemblyInfo(projectItem,project.Name);
}
}
}
在这里,我们利用文件顶部是否存在 © 声明来判断该文件是否已检查过。
解决了程序集信息后,我们需要遍历每个项目项并验证该项
foreach (ProjectItem item in project.ProjectItems)
{
CheckItems(item);
}
所以,最后,我们来看一下操作文本的代码。这是“FileContainsString
”的实现
private Boolean FileContainsString(ProjectItem item,
string strMatch, Boolean bFirstLineOnly)
{
Boolean bFound = false;
EnvDTE.Window theWindow = item.Open(Constants.vsViewKindCode);
//Get a handle to the new document in the open window
TextDocument textDoc =
theWindow.Document.Object("TextDocument") as TextDocument;
EditPoint editPoint = textDoc.StartPoint.CreateEditPoint();
if (editPoint != null)
{
string text = null;
if (bFirstLineOnly)
{
text = editPoint.GetLines(1,2);
}
else
{
text = editPoint.GetText(textDoc.EndPoint);
}
if (text != null)
{
bFound = (text.IndexOf(strMatch) != -1);
}
}
return bFound;
}
从现在开始,只需解析文件并使用 EditPoint::Insert
适当添加行即可。
虽然编写插件相当简单,但可用的文档还有很多不足之处,并且调试插件可能非常麻烦。你需要编译两个项目(插件和设置)并安装插件。当你按下 F5 时,Visual Studio 的新实例将启动并启动插件,你可以从 Visual Studio 的原始实例中调试它。
常见问题是每次调试会话前未卸载插件造成的。如果不行,你可能需要关闭所有 Visual S 实例,并通过最后一个幸存实例上的插件管理器删除插件。此时重启通常是个好策略!
附上一个完成的示例。希望这能对某些人有所帮助!享受!