Code InfoBox Visual Studio 扩展 (VSX) 2010






4.97/5 (39投票s)
一个放在代码画布右下角的 WPF 信息窗口,

目录
引言
自从我开始接触.NET以来,Visual Studio 一直是我最常使用的优秀工具之一。对于从 VB6 或更早版本迁移过来的用户而言,Visual Studio 始终是主要的开发工具。Visual Studio 不仅被我这样的开发者使用,也被学生、需要编写小型应用程序的在职专业人士、将其用作阅读器的人、以及编程学习者等广泛使用。在所有这些方面,Visual Studio 都表现出色。Visual Studio 也在不断地变得越来越强大。截至目前,Visual Studio 的最新版本是 Visual Studio 2010 Beta 2 [^]。因此,这个工具已经经历了一个漫长的演变过程。在此期间,涌现了许多新的语言,如 F# 等被引入 Visual Studio,而 J# 等语言则逐渐过时。但至今,Visual Studio 仍然是我们最重要的开发工具。
现在,通过编写简单的代码,可以轻松地扩展 Visual Studio。 Visual Studio 2010 Beta 2 [^] 附带了扩展包(Visual Studio SDK [^]),您可以免费轻松下载并为 Visual Studio 编写出色的扩展。安装 SDK 后,您会发现许多 Visual Studio 模板。每个模板都有其特定的扩展编写方式。
在本文中,我将讨论如何创建一个简单的 Visual Studio 2010 扩展,该扩展将为您提供关于您正在编写的代码的信息。
背景
我一直想尝试为这个优秀的工具编写一个扩展,但一直没有机会。尽管在早期版本的 Visual Studio 中,我需要学习和编写很多东西,比如向项目中添加互操作,正确并在合适的时间调用 API 相关方法等,但我始终没有时间去学习和编写。但现在,借助 Visual Studio 2010,一切都变得更加容易。我只需要编写几行代码,就可以为 Visual Studio 2010 创建一个很酷的扩展。
初始需求
以下是处理该项目所需的一些内容
- 由于这是一个 Visual Studio 2010 的扩展,您当然需要 Visual Studio IDE。您可以 在此处 [^] 找到它。
- Visual Studio 2010 Beta 2 SDK [^] 可用于在编写 Visual Studio 扩展 (VSX) 时创建自定义模板。
开始所需的就是这些。
已安装的模板类型
安装 Visual Studio SDK 后,您将获得一些新的模板。
- 编辑器视口装饰 (Editor ViewPortAdornment):此模板将为您的代码生成一个编辑器装饰。我将在此扩展中使用此模板。
- VSIX 项目 (VSIX Project):这是一个空的
VSIX
包模板。 - 编辑器边距 (Editor Margin):这会在编辑器底部创建一个边距窗格。
- 编辑器分类器 (Editor Classifier):它会为 Visual Studio 创建一个
编辑器分类器
。 - 编辑器文本装饰 (Editor Text Adornment):这会创建一个
编辑器装饰
项目,您可以在其中绘制到编辑器中。 - Windows Forms 工具箱控件 (Windows Forms Toolbox Control):帮助创建 Visual Studio 的工具箱控件。
- WPF 工具箱控件 (WPF Toolbox Control):基于 WPF 的工具箱控件。
您可以使用其中任何一个应用程序来构建您的自定义 Visual Studio 扩展。
注意:对于 Beta 版本,最好避免使用 VSIX 项目模板,因为您可能需要手动编辑源清单文件。 尽管修复这些文件很容易,但其他模板会自动为您完成。
信息窗口扩展
在开始讨论我如何创建扩展之前,让我们先弄清楚这个扩展是关于什么的。该扩展实际上会在代码窗口(您编写代码的地方)的右侧创建一个信息框。该信息横幅显示您源代码的统计信息。它主要显示:
- 文件中的总行数
- 文件中的总字符数
- 命名空间数量
- 字段数量
- 接口数量
- 类数量
- 方法数量
- 属性数量
- 注释数量
- 文件总大小(字节、KB、MB 等)

信息窗口会在您键入代码时立即更新,因此您在编写代码时会立即看到信息窗口中的更新。
除此之外,信息窗口基本上是使用 Expander
面板设计的。它将自身分为两个部分,每个部分都可以根据您的需要展开和折叠。因此,当它不需要时,您可以将其折叠起来,查看整个工作区域。下图显示了如何展开/折叠控件。

该控件有三种状态。第一种是一个小的可展开按钮,如图像的第一个部分所示。只需单击它,即可打开右侧面板,显示基本文件统计信息。可展开面板可以再次展开,以查看较低面板中定义的所有其他统计信息。
该控件除了显示内容外,还将保留在视口的最右侧角落,并在您向下滚动页面时相对于您的代码进行移动。此外,当您在编辑器中编写代码时,该控件还会动态更新。
创建扩展的步骤
该扩展实际上包含一个 WPF 用户控件,我将其放置在视口中。它还包含 2 个类,一个用于解析代码以获取关于代码的信息,另一个用于处理自定义编辑器事件并在页面加载时将 WPF 控件添加到视口。让我们一一来。
初始设置
我通过创建一个项目开始,从“可扩展性”部分选择“视口装饰项目”模板。这将创建一个包含一个 SourceManifest
文件和两个类的项目。一个是 Adornment
本身,用于放置装饰;另一个是 AdornmentFactory
类。我们将在本文后面讨论它们。
构建用户控件
右键单击项目,然后添加一个新的 WPF 用户控件。我使用了一个用户控件来简化这一点。该用户控件实际上包含一个 Expander
,其 ExpandDirection
设置为 Left
,它包装了几个 TextBlock
元素,以及另一个 Expander
,其 ExpandDirection
设置为 Down
。代码如下(为了简化,我删除了不必要的 UI 元素):
<Expander ExpandDirection="Left" Style="{DynamicResource ExpanderStyle1}"
x:Name="expMain" >
<StackPanel>
<TextBlock x:Name="txtNoLines"
Text="No of Lines : {0}"
Margin="25 25 25 0"
FontSize="12"
FontFamily="Verdana"
FontWeight="Bold"
Foreground="Yellow"></TextBlock>
<TextBlock x:Name="txtNoCharacters"
Text="No of Characters : {0}"
Margin="25 5 25 15"
FontSize="12"
FontFamily="Verdana"
FontWeight="Bold"
Foreground="Yellow"></TextBlock>
<Expander x:Name="expCodeInfo" ExpandDirection="Down"
Header="Code Information">
<StackPanel>
<TextBlock x:Name="txtClassInfo"
Margin="25 25 25 0"
FontSize="12"
FontFamily="Verdana"
FontWeight="Bold"
Foreground="LightYellow"/>
<Line
Margin="0,4"
SnapsToDevicePixels="True"
Stroke="Gold"
Stretch="Fill"
X1="0" X2="1"
/>
<TextBlock x:Name="txtFileSize"
Margin="25 5 25 15"
FontSize="12"
FontFamily="Verdana"
FontWeight="Bold"
Foreground="AliceBlue"/>
</StackPanel>
</Expander>
</StackPanel>
</Expander>
因此,您可以看到代码非常简单,有两个 Expanders
,一个用于基本统计信息,另一个用于增强统计信息。我使用 StackPanel
来固定 TextBlocks
的布局。
现在,如果您查看代码隐藏,它也和这个一样简单。我实际上创建了一个类来为我解析源文件。我只是在用户控件中添加了一个额外的构造函数,以使工作更加灵活。
private CodeInfoTracker _cinfo;
private CodeInfoTracker.Calculators _calculator;
public ucInfoBox(CodeInfoTracker cinfo)
: this()
{
this._cinfo = cinfo;
}
public void UpdateInfo(CodeInfoTracker info)
{
_calculator = info.PerFormCalculate();
this.txtNoLines.Text = string.Format("No of Lines : {0}",
_calculator.no_of_lines);
this.txtNoCharacters.Text = string.Format("No of Characters : {0}",
_calculator.no_of_characters);
this.txtFileSize.Text = string.Format("Total File Size : {0}",
_calculator.totalfilesize);
StringBuilder builder = new StringBuilder();
if (this._calculator.interfaces != 0)
builder.AppendFormat("Interfaces : {0}\n\r",
this._calculator.interfaces);
if (this._calculator.namespaces != 0)
builder.AppendFormat("NameSpaces : {0}\n\r",
this._calculator.namespaces);
if (this._calculator.classes != 0)
builder.AppendFormat("Classes : {0}\n\r",
this._calculator.classes);
if (this._calculator.methods != 0)
builder.AppendFormat("Methods : {0}\n\r", this._calculator.methods);
if (this._calculator.properties != 0)
builder.AppendFormat("Properties : {0}\n\r",
this._calculator.properties);
if (this._calculator.fields != 0)
builder.AppendFormat("Fields : {0}\n\r", this._calculator.fields);
if (this._calculator.comments != 0)
builder.AppendFormat("Comments : {0}\n\r", this._calculator.comments);
if (builder.Length > 0)
{
this.txtClassInfo.Visibility = System.Windows.Visibility.Visible;
this.txtClassInfo.Text = builder.ToString();
}
else
{
this.txtClassInfo.Text = "";
this.txtClassInfo.Visibility = System.Windows.Visibility.Hidden;
}
}
我使用了一个名为 Calculators
的结构,它位于我的自定义类中,该结构具有几个整数属性来保存解析源代码后的所有信息。info.PerFormCalculate();
提供解析结果。我在这里使用了所有这些信息并更新了 UIElement
。
构建获取信息的类
该类也非常直接,尽管代码中存在复杂性。此外,我还要感谢 CS Parser [^],它帮助我自动解析源代码。对于 VB 等语言,我是手动完成的。
该类实际上接收一个 IWpfTextView
对象,该对象表示 Visual Studio 文本编辑器。IWpfTextView
的实际实现是 WpfTextView
,它在实际执行期间由该类接收。我从 WPFTextView.TextSnapshot.GetText()
获取了源代码。
在我实际调用解析之前,我必须检测代码是用什么语言编写的。起初我打算自己检测,但谢天谢地,我在 WPFTextView
对象中发现了这个(尽管它在对象层次结构中隐藏得很深)。
public enum Language
{
CSharp, VisualBasic, Indeterminate
}
internal Language DetectLanguage
{
get
{
string langtype =
this._view.FormattedLineSource.TextAndAdornmentSequencer.
SourceBuffer.ContentType.DisplayName;
if(langtype.Equals("CSHARP",
StringComparison.InvariantCultureIgnoreCase))
return Language.CSharp;
else if(langtype.Equals("BASIC",
StringComparison.InvariantCultureIgnoreCase))
return Language.VisualBasic;
else
return Language.Indeterminate;
}
}
因此,DetectLanguage
使用 FormattedLineSource.TextAndAdornmentSequencer.
的
SourceBuffer.ContentType.DisplayNameWPFTextView
对象(我的天!)适当地为我提供了语言。之后,我创建了一个新方法 PerFormCalculate
,它将实际解析代码并为我提供 Calculation
结构的对象的实例。我故意没有在此处放置代码。
创建装饰工厂类
最后回到扩展,我需要创建一个 Adornment
的 Factory
类(InfoBoxAdornmentFactory
)。该类继承自 IWpfTextViewCreationListener
,这使我能够监听 WPF 编辑器创建事件。
[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("text")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class InfoBoxAdornmentFactory : IWpfTextViewCreationListener
{
[Export(typeof(AdornmentLayerDefinition))]
[Name("AlwaysVisibleInfoBox")]
[Order(After = PredefinedAdornmentLayers.Selection)]
[TextViewRole(PredefinedTextViewRoles.Interactive)]
public AdornmentLayerDefinition editorAdornmentLayer = null;
public void TextViewCreated(IWpfTextView textView)
{
new AlwaysVisibleInfoBox(textView);
}
}
因此,您可以看到我为该类使用了一些 Attributes
,例如 ContentType
(它定义我将只处理文本格式的编辑器调用)、TextViewRole
(它定义此类的 textview
类型)等。
在该类中,我创建了一个 AdornmentLayerDefination
对象。您可能会想,为什么需要它,即使我没有使用它?这只是为了与属性进行配置。Order
属性指定 InfoBox
将在每个图层选择后进行监听,Name
是编辑器扩展的名称等。构造函数只是从工厂调用我的 Adornment
类。
创建装饰类
Adornment
类是为了在实际执行时调用/处理适当的方法而创建的。您可能会注意到,我可以在我的 Factory
类中轻松完成所有事情,但我创建这个类是为了将代码与 Factory 元素分开。
装饰类实际上创建了一个 WPF 用户控件对象,并将其设置为视口 Canvas
。在构造函数中,我处理了 IWpfTextView.LayoutChanged
事件,该事件在布局或任何代码被修改时都会被调用。因此,我们可以通过此事件轻松获得回调,以便在我们编辑文档时。我还处理了 WPFTextView.ViewportHeightChanged
、WPFTextView.ViewportWidthChanged
来在浏览器编辑器大小改变时获得回调,以便我们可以相应地重新定位用户控件。
public AlwaysVisibleInfoBox(IWpfTextView view)
{
_view.LayoutChanged += this.OnLayoutChanged;
this.GetLayer();
}
private void GetLayer()
{
_adornmentLayer = this._view.GetAdornmentLayer("AlwaysVisibleInfoBox");
_view.ViewportHeightChanged += delegate { this.onSizeChange(); };
_view.ViewportWidthChanged += delegate { this.onSizeChange(); };
}
private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
this._info = new CodeInfoTracker(_view);
this.infobox.UpdateInfo(this._info);
}
public void onSizeChange()
{
_adornmentLayer.RemoveAllAdornments();
Canvas.SetLeft(infobox, _view.ViewportRight - 255);
Canvas.SetTop(infobox, _view.ViewportTop + 10);
_adornmentLayer.AddAdornment(AdornmentPositioningBehavior.ViewportRelative,
null, null,
infobox, null);
}
因此,构造函数只是调用 GetLayer
,它获取我们在 ViewPortSizeChage
事件(如 ViewportHeightChanged
和 ViewportWidthChanged
)中放置的 Layer
对象。在每次布局更改时,我都会更新用户控件,以清晰地反映更新。
因此,我们成功构建了我们的扩展。您可以使用 F5 运行它,它将打开 Visual Studio 的实验实例。
安装/卸载扩展
现在,安装和卸载扩展非常简单。编译项目时,它会生成一个 VSIX 文件。只需双击该文件,它就会自动安装到 Visual Studio。

要卸载文件,请打开 Visual Studio,转到“工具”->“扩展管理器”,然后选择“卸载”以卸载扩展。
发布您的扩展
将您的扩展发布到 Visual Studio Gallery 总是很棒的。您只需将 VSIX 文件上传到 www.visualstudiogallery.com
。我已经在 这里 [^] 上传了我的扩展。
注意:在发布扩展之前,请不要忘记更改清单文件。您只需双击清单文件即可获得酷炫的界面。
关注点
VSIX 文件只是使用 WindowsBase
的 PackagePart
类创建的 ZIP 存档。如果您打开任何 VSIX 文件,您将看到构成扩展的 DLL、清单文件和 [ContentType]
文件,这是 Office zip 版本的核心单元。
参考文献
关于此主题,我能找到的参考资料非常少。我找到的几项是:
Visual Studio Gallery 链接
我已将此 VSX 贡献给 Visual Studio Gallery:InfoBoxVSX
历史
- 初始版本 (V1.0):2010 年 1 月 29 日 我知道这个扩展还有很多工作要做,但我希望您会喜欢这个扩展。
- 第 2 版 (V1.1):2010 年 2 月 1 日
- 进行了小的错误修复并增加了对 VB 代码的支持。