为 VS.NET 构建重构插件 - 续集






4.70/5 (31投票s)
2004 年 8 月 16 日
7分钟阅读

162827

1900
介绍如何为原始的重构插件添加额外功能。
引言
本文基于现有的插件,展示了一个用于创建提供多种重构功能的插件的简单框架。它演示了如何与源代码模型交互,自动化源代码中的编辑功能,以及生成新类。
背景
一年前,我写了一篇文章,提供了一个 Visual Studio 插件,用于将变量重构为属性。自那时以来,这个插件在我工作中一直被广泛使用,并且我添加了其他几个重构功能。为此,我实现了一个小框架来简化添加其他重构功能。
该插件现在支持以下功能:
- 创建属性 - 指向一个变量,将其转换为带有 setter 和 getter 方法的属性。
- 创建函数 - 选择一段代码,将其转换为一个函数,并在原始位置插入对该函数的调用。
-
创建集合类 - 选择一个类,并生成一个类型的集合类(基于
CollectionBase
)。 - 创建模拟对象 - 选择一个类(甚至框架类),并生成一个集合类,该类覆盖所有可用的函数和属性,以便您模拟该类。
- 移动到基类 - 提取一个元素并将其移动到基类。
- 这是什么 - 一个简单的演示,展示了如何获取代码中的编辑点并确定它是做什么的。
插件框架
当您创建一个新的项目来构建 VS 插件时,VS.NET 会生成一个名为 connect
的类。该类包含 VS.NET 将调用的所有必要接口。有三种类型的调用:
- 初始化和清理调用:这些在启动时、加载插件时或 VS.NET 关闭时发生。
- 查询调用:此调用经常发生,每当 VS.NET 需要知道是否在其弹出菜单中启用您的命令时。您的代码必须确定具体情况,然后可以响应一个状态。
- 执行调用:您的插件命令已被调用,您需要运行它。
您不需要为每个命令拥有一个 connect
类,但是如果您在此类中公开多个命令(如此处所示),则 connect 调用必须在 Query 和 Exec 调用中确定哪个命令受影响。
插件框架通过创建一个名为 VS-Plugin
的基类来解决这个问题。上面列出的每种类型的命令都实现为 VS-Plugin
的子类。connect
类现在只知道这些插件的集合,当 Query 或 Exec 调用发生时,它只需遍历其集合并将请求传递给相应的类。
VSPlugIn
类还包含几个实用函数,用于执行以下操作:
- 当前高亮的是什么,类、函数等。
- 将必要命令结构链接到 VS.NET 的能力。
- 弹出对话框以向用户询问目标项目。
- 在 VS.NET 的“输出”窗口中显示消息。
- 等等。
大多数插件还有一个单独的类来执行实际工作 - 例如,MakeMockObject
插件类有一个名为 MockObjectBuilder
的类。这是一种简单常见的“关注点分离”,插件类的目的是理解请求的细节并将结果代码传递到正确的位置,而 MockObjectBuilder
类的目的是生成 Mock 类。
如何添加另一个插件
只需要几个步骤:
1. 创建一个新的 VSPlugIn 子类
VSPlugIn
有几个抽象属性和方法,您必须实现它们:
-
cmdName
:简短命令,例如:“MakeProperty
”。 -
qualifiedName
:完全限定命令,例如:“RefactorAddIn.Connect.MakeProperty
”。 -
IconId
:显示在菜单中的图标 ID,例如:54。 -
position
:命令在菜单中的位置(1=第一个)。 -
shortDescription
:菜单中显示的文本。 - long
Description
:工具提示的文本。
-
doQueryStatus()
:实现逻辑以确定方法当前是否有意义,是否应激活或非激活(或隐藏)。 -
doExec()
:实现命令的实际操作。
2. 扩展 Connect 类
connect
类有一个名为 OnStartupComplete
的方法。您必须将您的插件添加到其他插件的集合中,并调用方法使其显示在正确的工具栏上。
CommandBar popup = InsertSubMenu("Code Window", "Refactoring");
CommandBar classViewCommandBar = applicationObject.CommandBars["Class View Item"];
VSPlugIn plugin = new PropertyMaker(applicationObject, addInInstance);
Plugins.Add(plugin);
plugin.InsertCommand(classViewCommandBar,false);
plugin.InsertCommand(popup, false);
前两行已经给出,它们提供了两个命令栏,一个是独立的弹出菜单,属于代码窗口菜单(在代码窗口右键单击时弹出的那个),另一个是右键单击类资源管理器时弹出的菜单。
您需要添加与上面最后四行类似的代码:
- 创建您的插件实例。
- 将插件添加到插件集合中。
- 如果您希望插件在每个命令栏上都可用,请调用插件的
InsertCommand
方法。布尔值仅在开发期间才应为 true - 参见下面的“兴趣点”。
上面的示例使属性生成器在两个菜单上都可用。您可以定位其他菜单栏,也可以完全不放置它们。在这种情况下,您仍然可以通过“工具”->“选项”对话框(选择“环境”文件夹和“键盘”项)中的键盘映射来绑定到您的命令 - 或者您可以通过宏调用您的命令。
关注点
编程 Visual Studio.NET 不是一件容易的事。良好的文档不容易获得。然而,我发现“Inside Microsoft Visual Studio. NET”(Brian Johnson, Craig Skibo, Marc Young, Microsoft Press, ISBN 0-7356-1874-7)这本书非常有帮助。但您仍需要仔细阅读几遍。
Visual Studio .NET 是用 COM 编写的,这使得工作不那么愉快。以下是一些要点:
- 集合的索引从 1 开始。
- 如果您向一个集合请求它没有的东西,您不会得到 null 而是得到一个异常。所以如果您想检查某物是否在集合中,您必须这样写:
protected bool HasCommand(string commName,out Command command) { command = null; try { Commands commands = _applicationObject.Commands; command = commands.Item(commName,-1); return true; } catch (System.Exception){} return false; }
- 并非所有命令在所有情况下都有效。例如,
InsertClass
命令(用于将新类插入代码模型)仅适用于 C++,不适用于 C# 或 VB.NET。但是,InsertMethod
可以。 - 并非所有窗口都输出其内容。尽管文档非常努力地告诉我们如何访问某些窗口中的树形控件,但您需要仔细阅读才能发现,对于某些窗口(例如类视图窗口),这是不可用的。
- 强烈建议您研究和理解其他插件示例。非常感谢 Erick Sgarbi 和他关于 VS 插件的文章,我从中借鉴了几种技术,包括上面介绍的那种。
- 如果您对 CodeDom(定义代码结构然后生成源代码)感兴趣,请参考我的
CollectionBuilder
类作为示例。 - 当您将插件发布到 Visual Studio 时,您会创建一个称为命令的对象。该命令是 VS.NET 中其他方识别您的插件并调用它的结构。请注意,在关闭时,VS.NET 会保存此命令并在重新启动时重新加载它。因此,您不必在每次加载插件时都重新创建命令。实际上,您不应该这样做,因为这会扰乱系统。
VSPlugIn::InsertCommand
方法有一个名为deleteIfExists
的布尔参数;如果为 true,它将在创建新命令之前删除现有命令。此参数仅在开发和调试插件期间应为 true。在默认模式下(=false),它会尝试查找并使用现有命令,仅在不存在时创建新命令。 - 在调试期间,还可以创建多个控件实例(使命令在弹出菜单中可见的那些)。
InsertCommand
方法尝试通过调用flushControls()
来清除这些。如果您不这样做,可能会发生各种奇怪的问题,让您抓狂。
历史
- 版本 1.1 - 基于反馈,我添加了几项更改:
- 现在可以配置“创建属性”中私有变量的命名方式。
- 安装程序会在注册表中添加一个条目:
HKEY_CURRENT_USER\ Software\ MethodsConsulting\ RefactorAddIn\ PrivatevarCase
。可能的值有:- 下划线 - 在变量前加上下划线
- m_下划线 - 在变量名称前加上“m_”
- 驼峰 - 使用驼峰命名法
- “移动到基类”现在支持变量、属性和函数。
- 以及修复了各种小烦扰。
尽情享受吧