基本介绍篇
在实现这个扩展之前,让我们先弄清楚这个扩展实现什么功能。这个扩展实际上是在你的VS窗口的右上角创建了一个信息框代码。该信息框显示您的源代码的统计信息。这些信息包括:
1、文件的代码行数
2、文件的字符数
3、命名空间的个数
4、字段个数
5、接口个数
6、类的个数
7、函数个数
8、属性个数
9、注释的数量
10、统计文件的大小(Bytes, KB, MB等)。
当您键入您的代码,你会看到信息窗口中的数据会即时更新。
这个信息窗口,是利用可扩展面板。你可以把它看成两个部分,每一个部分都可以根据您的需要展开和折叠。这样,当你不需要它时,你将它可以折叠起来,需要的时候,将它展开。下面演示如何展开/折叠这个控件。
这个控件有三个特殊的状态。第一个状态是一个很小的扩展按钮。如上图的第一部分。只需点击它,就会打开控件右侧的面板,这个面板显示文件的基本数据,如上图的第二部分。这个控件还有一个可扩展面板,如果点击扩展,就会看到下面的面板,其中显示其他的统计数据,如上图的第三部分。
实现篇:
需要软件:
1、 Microsoft Visual Studio 2010
2、 Visual Studio 2010 SDK
你安装 Visual Studio SDK之后,你的Visual Studio 2010中会多出下面这些模板:
这篇文章中,我们使用模板Editor ViewPort Adornment实现这个扩展,此模板将为你的代码编辑器的带来一个装饰品。
其实这个扩展包一个WPF用户控件,我把它放进VS的视窗中就成了信息框。它还含有两个类,一个类用来解析代码,获取代码的相关信息;另一个类用来处理自定义编辑器的事件和当页以及加载的时候将WPF控件添加到页面中。
第一步:创建一个Viewport Adornment项目
我们从Extensibility中选择Viewport Adornment模板创建一个项目。这将生成一个SourceManifest文件和两个类文件。一个是Adornment类本身,另外一个是AdornmentFactory类。
第二步:添加一个WPF用户控件
右键单击项目,选择添加一个新的WPF用户控件。为了简单起见,我使用了一个用户控件。这个用户控件实际上包含一个Expander控件,设置它的ExpandDirection = Left,它里面又包含了一些TextBlock控件和另外一个Expander ,设置里面的这个Expander的ExpandDirection = Down。看下面的代码(我删除不必要的元素,使其更简单):
2 x:Name ="expMain" >
3 < StackPanel >
4 < TextBlock x:Name ="txtNoLines"
5 Text ="No of Lines : {0}"
6 Margin ="25 25 25 0"
7 FontSize ="12"
8 FontFamily ="Verdana"
9 FontWeight ="Bold"
10 Foreground ="Yellow" ></ TextBlock >
11 < TextBlock x:Name ="txtNoCharacters"
12 Text ="No of Characters : {0}"
13 Margin ="25 5 25 15"
14 FontSize ="12"
15 FontFamily ="Verdana"
16 FontWeight ="Bold"
17 Foreground ="Yellow" ></ TextBlock >
18 < Expander x:Name ="expCodeInfo" ExpandDirection ="Down"
19 Header ="Code Information" >
20 < StackPanel >
21 < TextBlock x:Name ="txtClassInfo"
22 Margin ="25 25 25 0"
23 FontSize ="12"
24 FontFamily ="Verdana"
25 FontWeight ="Bold"
26 Foreground ="LightYellow" />
27 < Line
28 Margin ="0,4"
29 SnapsToDevicePixels ="True"
30 Stroke ="Gold"
31 Stretch ="Fill"
32 X1 ="0" X2 ="1"
33 />
34 < TextBlock x:Name ="txtFileSize"
35 Margin ="25 5 25 15"
36 FontSize ="12"
37 FontFamily ="Verdana"
38 FontWeight ="Bold"
39 Foreground ="AliceBlue" />
40 </ StackPanel >
41 </ Expander >
42 </ StackPanel >
43 </ Expander >
你可以看到,代码很简单,两个Expanders,一个用来显示基本的统计信息和另外一个显示扩展的统计信息。我还使用StackPanel来固定TextBlocks布局。
现在,如果你看一下后台代码,发现它也一样简单。其实我已经创建了一个CodeInfoTracker类,用它来为我们分析源代码文件。我只是为我们的用户控件添加了一个构造函数,使用户控件更具扩展性而已。
2 private CodeInfoTracker.Calculators _calculator;
3 public ucInfoBox(CodeInfoTracker cinfo)
4 : this ()
5 {
6 this ._cinfo = cinfo;
7 }
8 public void UpdateInfo(CodeInfoTracker info)
9 {
10 _calculator = info.PerFormCalculate();
11 this .txtNoLines.Text = string .Format( " No of Lines : {0} " ,
12 _calculator.no_of_lines);
13 this .txtNoCharacters.Text = string .Format( " No of Characters : {0} " ,
14 _calculator.no_of_characters);
15 this .txtFileSize.Text = string .Format( " Total File Size : {0} " ,
16 _calculator.totalfilesize);
17
18 StringBuilder builder = new StringBuilder();
19 if ( this ._calculator.interfaces != 0 )
20 builder.AppendFormat( " Interfaces : {0}\n\r " ,
21 this ._calculator.interfaces);
22 if ( this ._calculator.namespaces != 0 )
23 builder.AppendFormat( " NameSpaces : {0}\n\r " ,
24 this ._calculator.namespaces);
25 if ( this ._calculator.classes != 0 )
26 builder.AppendFormat( " Classes : {0}\n\r " ,
27 this ._calculator.classes);
28 if ( this ._calculator.methods != 0 )
29 builder.AppendFormat( " Methods : {0}\n\r " , this ._calculator.methods);
30 if ( this ._calculator.properties != 0 )
31 builder.AppendFormat( " Properties : {0}\n\r " ,
32 this ._calculator.properties);
33 if ( this ._calculator.fields != 0 )
34 builder.AppendFormat( " Fields : {0}\n\r " , this ._calculator.fields);
35 if ( this ._calculator.comments != 0 )
36 builder.AppendFormat( " Comments : {0}\n\r " , this ._calculator.comments);
37
38 if (builder.Length > 0 )
39 {
40 this .txtClassInfo.Visibility = System.Windows.Visibility.Visible;
41 this .txtClassInfo.Text = builder.ToString();
42 }
43 else
44 {
45 this .txtClassInfo.Text = "" ;
46 this .txtClassInfo.Visibility = System.Windows.Visibility.Hidden;
47 }
48 }
使用了一个结构体Calculators ,这个结构体放置在我们的自定义类中,它有几个int属性用来保存分析源文件获取的所有信息。 info.PerFormCalculate();
给出分析的结果。这里使用的所有获取的信息来更新了UIElements。
第三步:创建获取源文件信息的类
虽然代码存在一些复杂性,但是这个类其实很简单。我很感谢CS Parser [^],它帮助我自动地解析源代码。
这个类需要一个IWpfTextView对象,它代表着Visual Studio文本编辑器。实际上WpfTextView实现了IWpfTextView。在执行期间这个类接受这个对象。我可以从WPFTextView.TextSnapshot.GetText()获得到了源代码。
在我调用的这个分析的时候,我只需要检测的代码是什么语言写的。开始我想自己来实现,但是感谢上帝,我在WPFTextView中发现已经存在这个对象了。
2 {
3 CSharp, VisualBasic, Indeterminate
4 }
5 internal Language DetectLanguage
6 {
7 get
8 {
9 string langtype =
10 this ._view.FormattedLineSource.TextAndAdornmentSequencer.
11 SourceBuffer.ContentType.DisplayName;
12 if (langtype.Equals( " CSHARP " ,
13 StringComparison.InvariantCultureIgnoreCase))
14 return Language.CSharp;
15 else if (langtype.Equals( " BASIC " ,
16 StringComparison.InvariantCultureIgnoreCase))
17 return Language.VisualBasic;
18 else
19 return Language.Indeterminate;
20 }
21 }
DetectLanguage妥善地利用WPFTextView对象的FormattedLineSource.TextAndAdornmentSequencer.
SourceBuffer.ContentType.DisplayName,这个属性告诉我是使用了哪种语言。之后我创建了一个新的方法PerFormCalculate,用它来解析源代码,它返回一个Calculation结构对象。
第四步:创建 Adornment Factory 类
回到这个扩展,我创建一个Adornment(InfoBoxAdornmentFactory)的Factory类。这个类继承IWpfTextViewCreationListener,用来监听WPF的编辑和创建事件。
2 [ContentType( " text " )]
3 [TextViewRole(PredefinedTextViewRoles.Document)]
4 internal sealed class InfoBoxAdornmentFactory : IWpfTextViewCreationListener
5 {
6 [Export( typeof (AdornmentLayerDefinition))]
7 [Name( " AlwaysVisibleInfoBox " )]
8 [Order(After = PredefinedAdornmentLayers.Selection)]
9 [TextViewRole(PredefinedTextViewRoles.Interactive)]
10 public AdornmentLayerDefinition editorAdornmentLayer = null ;
11 public void TextViewCreated(IWpfTextView textView)
12 {
13 new AlwaysVisibleInfoBox(textView);
14 }
15 }
这里,你可以看到我在这个类上使用了很多Attributes,像ContentType,它定义了我们只处理文本格式的编辑器;还有TextViewRole,它定义了将被这个类处理的textview的类型。
在这个类中,我创建了一个AdornmentLayerDefination对象。可能你想知道我们没有使用它,无什么还需要定义它呢,它只是用来配置属性的。Order属性指定,当,InfoBox在层被选之后监听,Name是编辑扩展的名字。
第五步:创建Adornment 类
Adornment类实际创建了一个WPF用户控件对象,并设置它的视图画布。在内部构造函数中,我处理IWpfTextView.LayoutChanged事件,当代码修改或者布局改变的时候,就触发这个事件。因此,通过这一事件,当我们编辑的文档时,我们可以很容易地得到回调。当浏览器编辑器的大小改变时,我还通过处理WPFTextView.ViewportHeightChanged,WPFTextView.ViewportWidthChanged得到回调,使我们可以重新定位相应的UserControl。
2 {
3 _view.LayoutChanged += this .OnLayoutChanged;
4 this .GetLayer();
5 }
6 private void GetLayer()
7 {
8 _adornmentLayer = this ._view.GetAdornmentLayer( " AlwaysVisibleInfoBox " );
9 _view.ViewportHeightChanged += delegate { this .onSizeChange(); };
10 _view.ViewportWidthChanged += delegate { this .onSizeChange(); };
11 }
12 private void OnLayoutChanged( object sender, TextViewLayoutChangedEventArgs e)
13 {
14 this ._info = new CodeInfoTracker(_view);
15 this .infobox.UpdateInfo( this ._info);
16 }
17 public void onSizeChange()
18 {
19
20 _adornmentLayer.RemoveAllAdornments();
21 Canvas.SetLeft(infobox, _view.ViewportRight - 255 );
22 Canvas.SetTop(infobox, _view.ViewportTop + 10 );
23
24 _adornmentLayer.AddAdornment(AdornmentPositioningBehavior.ViewportRelative,
25 null , null ,
26 infobox, null );
27 }
因此,构造函数只是调用GetLayer来获取的Layer对象,发生在ViewportHeightChanged和ViewportWidthChanged ViewPortSizeChage事件。当一个布局改变时,我就能更新这个用户的控件。
至此,我们成功地建立我们的扩展。你可以使用F5运行它,它会打开一个Visual Studio的Experimental实例。
安装和卸载这个扩展:
安装和卸载这个扩展是非常容易的。当您编译项目后,它会产生一个VSIX文件。您可以只需双击这个文件,它会自动安装到Visual Studio。
要卸载的文件,您打开Visual Studio,转到 Tools - > Extension Manager,然后选择卸载该扩展。
发布您的扩展:
发布你的扩展到Visual Studio库的方式也非常的酷。只要你需要上传VSIX文件到http://www.visualstudiogallery.com/。我已经上载我的这个扩展。
总结:这篇文章,从头到尾一步一步教你实现一个很简单很酷的VS2010的扩展
本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2010/04/15/Visual-Studio-Extension.html,如需转载请自行联系原作者