65.9K
CodeProject 正在变化。 阅读更多。
Home

Code InfoBox Visual Studio 扩展 (VSX) 2010

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (39投票s)

2010年1月28日

CPOL

10分钟阅读

viewsIcon

128751

downloadIcon

1735

一个放在代码画布右下角的 WPF 信息窗口, 提供代码统计信息

InfoBoxAdornment_Src

目录

引言

自从我开始接触.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 创建一个很酷的扩展。

初始需求

以下是处理该项目所需的一些内容

  1. 由于这是一个 Visual Studio 2010 的扩展,您当然需要 Visual Studio IDE。您可以 在此处 [^] 找到它。
  2. Visual Studio 2010 Beta 2 SDK [^] 可用于在编写 Visual Studio 扩展 (VSX) 时创建自定义模板。

开始所需的就是这些。

已安装的模板类型

安装 Visual Studio SDK 后,您将获得一些新的模板。
snap2.JPG

  • 编辑器视口装饰 (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 等)
snap1.JPG

信息窗口会在您键入代码时立即更新,因此您在编写代码时会立即看到信息窗口中的更新。

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

snap3.JPG

该控件有三种状态。第一种是一个小的可展开按钮,如图像的第一个部分所示。只需单击它,即可打开右侧面板,显示基本文件统计信息。可展开面板可以再次展开,以查看较低面板中定义的所有其他统计信息。
该控件除了显示内容外,还将保留在视口的最右侧角落,并在您向下滚动页面时相对于您的代码进行移动。此外,当您在编辑器中编写代码时,该控件还会动态更新。

创建扩展的步骤

该扩展实际上包含一个 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.DisplayName
WPFTextView 对象(我的天!)适当地为我提供了语言。之后,我创建了一个新方法 PerFormCalculate,它将实际解析代码并为我提供 Calculation 结构的对象的实例。我故意没有在此处放置代码。

创建装饰工厂类

最后回到扩展,我需要创建一个 AdornmentFactory 类(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.ViewportHeightChangedWPFTextView.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 事件(如 ViewportHeightChangedViewportWidthChanged)中放置的 Layer 对象。在每次布局更改时,我都会更新用户控件,以清晰地反映更新。

因此,我们成功构建了我们的扩展。您可以使用 F5 运行它,它将打开 Visual Studio 的实验实例。

安装/卸载扩展

现在,安装和卸载扩展非常简单。编译项目时,它会生成一个 VSIX 文件。只需双击该文件,它就会自动安装到 Visual Studio。

snap4.JPG

要卸载文件,请打开 Visual Studio,转到“工具”->“扩展管理器”,然后选择“卸载”以卸载扩展。

发布您的扩展

将您的扩展发布到 Visual Studio Gallery 总是很棒的。您只需将 VSIX 文件上传到 www.visualstudiogallery.com。我已经在 这里 [^] 上传了我的扩展。

注意:在发布扩展之前,请不要忘记更改清单文件。您只需双击清单文件即可获得酷炫的界面。

关注点

VSIX 文件只是使用 WindowsBasePackagePart 类创建的 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 代码的支持。
© . All rights reserved.