原文:http://www.codeproject.com/useritems/LineCounterAddin.asp
注意:1.本文中的提到的“外接程序”等同与“插件程序”
2.本文提供的源码是在原作的基础上有过修改。
代码行数统计器(一):http://sifang2004.cnblogs.com/archive/2006/06/26/436128.html
代码行数统计器(二)
--Visual Studio 2005插件开发
代码行数统计器(一)
为你的命令使用自定义图标
当你创建了一个提供菜单项和工具栏按钮的Visual Studio插件时,Visual Studio将用默认的Microsoft Office图标来设置命令,特殊的,就使用一个黄色的笑脸图标(该图标的索引是59),该图标就如下图所示:

通常,可用的图标是作为MSO库的一部分,你是不能随便能查找到的。为你的命令使用自定义的图标也不是特别难,但是关于任何做这些的文档都非常隐秘,不是那么容易找到。
第一步就是在插件工程中加入一个资源文件,并把它的“Build Action”属性改为“None”,我们将在post-build事件中对该文件进行我们自己的处理。


现在有了一个新的资源文件,我们需要把一个图片添加进去,这你可以根据下图来完成:

当弹出对图片命名的框时,我们简单命名为“1”,所有Visual Studio引用的图片资源都是通过它们的索引号,资源的ID应该和它的索引是相同的。本插件仅需要一个图片。一旦图片加入了,打开它,把它的尺寸该为16*16px,且它的颜色深度是16色,Visual Studio仅显示那些颜色深度4-24的。具体如何设计该图片你可以自由发挥了,当然如果你不想这么做,你可以从我的源码中下载来覆盖就行了。
当你正确地创建一个资源文件并添加了图片后,我们将要设置正确地不编译它,这种特殊的资源文件应该编译成一个卫星程序集(对于各种资源文件的解释,你可以看的令一篇文章《资源与本地化》),我们可以随着Post-build事件来完成,打开LineCounterAddin工程的属性窗口,按照下图找到Build Events标签。

d:
cd $(ProjectDir)
mkdir $(ProjectDir)$(OutDir)en-US
"$(DevEnvDir)..\..\SDK\v2.0\Bin\Resgen" $(ProjectDir)ResourceUI.resx
"$(SystemRoot)\Microsoft.NET\Framework\v2.0.50727\Al" /embed:$(ProjectDir)ResourceUI.resources/culture:en-US /out:$(ProjectDir)$(OutDir)en-US\LineCounterAddin.resources.dll
del $(ProjectDir)ResourceUI.resources
注意:你一定得更改第一行“d:”,它表示你工程所在的驱动器,这一点很重要,否则Resgen命令ResourceUI.resx文件引用的文件,同时也要注意,我们必须安装了.NET 2.0 SDK,否则Resgen命令就是不可用的,该脚本通常是以别的方式工作的,因为它是基于宏指令(macros)而不是指定的路径,当你把这段脚本放到Post-build那地方后,每次当你编译工程或者解决方案时会为你的插件编译生成一个卫星程序集,它将放在你的编译输出文件夹的一个子目录en-US下,当运行本工程时,Visual Studio将会引用卫星程序集以便找到任何命令行图片。下面就是使用使用了自定义的图标的效果:

计算行数
现在我们已经明白了如何创建一个显示新的工具窗口的插件。现在是时候去看看一些精髓代码了,插件的这一块就向编写老式的windows窗口应用,有着用户接口,事件处理以及辅助函数。对于该程序的需求是十分简单的,几个基本的设计模式将帮助我们达到这些要求。
² 主要目标:显示解决方案中每一个工程行数的计算信息。
² 显示解决方案的重要的统计计算以及每个工程的统计计算。
² 显示工程中每个单独的可计算的文件的行数计算信息。
² 正确地为不同类型的源文件计算行数,例如C++,C#,VB,XML等等。
² 允许按名称,行数,文件扩展名排序文件列表。
² 允许按文件类型,工程分组文件列表,也可用不分组。
² 在重新计算时显示处理进程。
让我们从给自己一个简洁,结构良好的用户控件的源文件开始吧。你的用户控件的源文件也应该有如下结构:
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Microsoft.VisualStudio.CommandBars;
using Extensibility;
using EnvDTE;
using EnvDTE80;
namespace LineCounterAddin
{
public partial class LineCounterBrowser : UserControl
{
#region Nested Classes
// IComparer classes for sorting the file list
#endregion
#region Constructor
#endregion
#region Variables
private DTE2 m_dte; // Reference to the Visual Studio DTE object
#endregion
#region Properties
/// <summary>
/// Recieves the VS DTE object
/// </summary>
public DTE2 DTE
{
set
{
m_dte = value;
}
}
#endregion
#region Handlers
// UI Event Handlers
#endregion
#region Helpers
#region Line Counting Methods
// Line counting methods for delegates
#endregion
#region Scanning and Summing Methods
// Solution scanning and general line count summing
#endregion
#endregion
}
#region Support Structures
// Delegate for pluggable line counting methods
delegate void CountLines(LineCountInfo info);
/// <summary>
/// Encapsulates line count sum details.
/// </summary>
class LineCountDetails
{
// See downloadable source for full detail
}
/// <summary>
/// Wraps a project and the line count total detail
/// for that project. Enumerates all of the files
/// within that project.
/// </summary>
class LineCountSummary
{
// See downloadable source for full detail
}
/// <summary>
/// Wraps a project source code file and the line
/// count info for that file. Also provides details
/// about the file type and what icon should be shown
/// for the file in the UI.
/// </summary>
class LineCountInfo
{
// See downloadable source for full detail
}
#endregion
}
从上面这些基本的代码中,你就应该能推断出一些窍门,我们是怎么正确计算各种不同类型的源文件的行数的,以及如何以不同的方法排序的。
现在,我们就要处理如何无缝地处理允许多种计算规则的,而不是用丑陋的if / else或者switch语句,现代语句最大的一个特点就是函数指针,在.NET中是以委托的形式提供的。很多时候,我认为在.NET中委托的价值被极度忽略了,所以,我提供一个简单但却非常优雅的例子,聪明的程序员如何让他们的生活更加简单,这个概念是简单的:建立一个扩展名和委托的行数计算函数直接的映射列表,使用.NET2.0和泛型,我们能非常有效地做到这点。在下面的地方这样更新你的源码:
#region Constructor
/// <summary>
/// Construct the line counter user interface and
/// the countable file type mappings (to icons and
/// counting algorithms).
/// </summary>
public LineCounterBrowser()
{
InitializeComponent();
m_cfgMgr = ConfigManager.Instance;
}
#endregion
这行代码是非常重要的,就是我们刚才提到的建立映射列表:
m_cfgMgr = ConfigManager.Instance;
我们可以根据这行代码顺藤摸瓜,就可以发现真正重要的是下面的方法已经其调用的几个方法,就是这些方法建立映射表:
private void Initialize()
{
if (m_init)
return;
try
{
// Extract the config file path from the default .config file
string configPath = ConfigurationManager.AppSettings["configPath"];
// If the default .config file is missing...
if (configPath == null)
{
// Get the assembly...
Assembly asm = Assembly.GetExecutingAssembly();
string exeRoot = asm.Location;
try
{
// And try explicitly loading it...
Configuration config = ConfigurationManager.OpenExeConfiguration(exeRoot);
configPath = config.AppSettings.Settings["configPath"].Value;
config = null;
// And if it is still not found...
if (configPath == null)
{
// Directly try to load the file from the same path as the .exe
string configRoot = Path.GetDirectoryName(exeRoot);
configPath = configRoot + "LineCounterAdding.config";
}
}
catch (ConfigurationErrorsException)
{
// Directly try to load the file from the same path as the .exe in all other cases
string configRoot = Path.GetDirectoryName(exeRoot);
configPath = configRoot + "LineCounterAddin.config";
}
asm = null;
}
// Replace the $(PersonalFolder) macro with the users MyDocuments path
configPath = configPath.Replace("$(PersonalFolder)", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
// Load the config file
if (File.Exists(configPath))
{
m_doc = new XmlDocument();
m_doc.Load(configPath);
configPath = null;
nsmgr = new XmlNamespaceManager(m_doc.NameTable);
nsmgr.AddNamespace(prefix, "http://synapticeffect.com/Config.xsd");
}
if (m_doc != null)
{
// Process the projecttypes section
XmlElement elProjectTypes = FindConfigElement("//" + prefix + ":projecttypes");
ProcessProjectTypes(elProjectTypes);
elProjectTypes = null;
// Process the filetypes section
XmlElement elFileTypes = FindConfigElement("//" + prefix + ":filetypes");
ProcessFileTypes(elFileTypes);
elFileTypes = null;
// Process the countparsers section
XmlElement elCountParsers = FindConfigElement("//" + prefix + ":countparsers");
ProcessCountParsers(elCountParsers);
elCountParsers = null;
// TODO: Process the metricsparsers section
}
m_init = true;
}
catch
{
}
finally
{
}
}
明眼人很快就明白,代码中读取了一个叫LineCounterAddin.config的配置文件,为了更容易扩展和配置,我们把与计算行数相关的映射信息都包含在这个文件中,下面就是该文件的一部分,没有包含全部内容但框架是完整的,在根元素linecounter下包含了四个子元素,projecttypes,filetypes,countparsers,metricparsers。根据内容大家都基本明白该文件的意义所在了,我仅解释下filetype元素,extension属性表明是何种类型的文件,<icon>子元素指示针对于该种文件类型图标,<for>子元素中<allow>元素指示当计算行数时我们应该使用到的方法,通过解析该文件我们就很容易建立起一种映射。
<?xml version="1.0" encoding="utf-8" ?>
<linecounter xmlns="http://synapticeffect.com/Config.xsd">
<projecttypes>
<projecttype key="{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}" name="C# Project">
<icon iconIndex="1" />
</projecttype>
<projecttype key="{B5E9BD33-6D3E-4B5D-925E-8A43B79820B4}" name="VB.NET Project">
<icon iconIndex="2" />
</projecttype>
</projecttype>
</projecttypes>
<filetypes>
<filetype extension=".cs" desc="C# File">
<icon iconIndex