Visual Studio Add-In 扩展VS 简易CodeReview插件
最近装上了VS11,发现VS11 RC中找不到宏管理器了,之前用宏命令写的CodeReview插件也没有办法使用了。索性写下Add-In版本的
新建VS外接程序项目,之后向导中的选项都可以不选
完成之后编辑Connect.cs文件
using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
namespace CodeReview
{
/// <summary>用于实现外接程序的对象。</summary>
/// <seealso class='IDTExtensibility2' />
public class Connect : IDTExtensibility2, IDTCommandTarget
{
private DTE2 _applicationObject;
private AddIn _addInInstance;
CommandBar stdCmdBar;
CommandBarControl stdCmdBarCtl;
/// <summary>实现外接程序对象的构造函数。请将您的初始化代码置于此方法内。</summary>
public Connect()
{
}
/// <summary>实现 IDTExtensibility2 接口的 OnConnection 方法。接收正在加载外接程序的通知。</summary>
/// <param term='application'>宿主应用程序的根对象。</param>
/// <param term='connectMode'>描述外接程序的加载方式。</param>
/// <param term='addInInst'>表示此外接程序的对象。</param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if (connectMode == ext_ConnectMode.ext_cm_AfterStartup || connectMode == ext_ConnectMode.ext_cm_Startup)
{
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands;
//string toolsMenuName = "Tools";
//将此命令置于"工具"菜单上。
//查找 MenuBar 命令栏,该命令栏是容纳所有主菜单项的顶级命令栏:
//Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
//在 MenuBar 命令栏上查找"工具"命令栏:
//CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
//CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
//如果希望添加多个由您的外接程序处理的命令,可以重复此 try/catch 块,
// 只需确保更新 QueryStatus/Exec 方法,使其包含新的命令名。
try
{
//将一个命令添加到 Commands 集合:
Command command = commands.AddNamedCommand2(_addInInstance, "CodeReview", "CodeReview", "Simple CodeReview for VS2012", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
//将对应于该命令的控件添加到"工具"菜单:
//if ((command != null) && (toolsPopup != null))
//{
// command.AddControl(toolsPopup.CommandBar, 1);
//}
//添加命令集合
stdCmdBar = commands.AddCommandBar("CodeReview VS11", vsCommandBarType.vsCommandBarTypeToolbar, null, 1) as CommandBar;
//添加命令button
stdCmdBarCtl = command.AddControl(stdCmdBar, stdCmdBar.Controls.Count + 1) as Microsoft.VisualStudio.CommandBars.CommandBarControl;
stdCmdBarCtl.Caption = "Click for CodeReview";
//按扭可用
stdCmdBarCtl.Enabled = true;
}
catch (Exception ex)
{
//如果出现此异常,原因很可能是由于具有该名称的命令
// 已存在。如果确实如此,则无需重新创建此命令,并且
// 可以放心忽略此异常。
}
}
}
private void OnTextLineChange(TextPoint start,TextPoint end,int hint)
{
System.Windows.Forms.MessageBox.Show("selection");
}
/// <summary>实现 IDTExtensibility2 接口的 OnDisconnection 方法。接收正在卸载外接程序的通知。</summary>
/// <param term='disconnectMode'>描述外接程序的卸载方式。</param>
/// <param term='custom'>特定于宿主应用程序的参数数组。</param>
/// <seealso class='IDTExtensibility2' />
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
if (stdCmdBarCtl != null)
stdCmdBarCtl.Delete();
if (null != stdCmdBar)
stdCmdBar.Delete();
}
/// <summary>实现 IDTExtensibility2 接口的 OnAddInsUpdate 方法。当外接程序集合已发生更改时接收通知。</summary>
/// <param term='custom'>特定于宿主应用程序的参数数组。</param>
/// <seealso class='IDTExtensibility2' />
public void OnAddInsUpdate(ref Array custom)
{
}
/// <summary>实现 IDTExtensibility2 接口的 OnStartupComplete 方法。接收宿主应用程序已完成加载的通知。</summary>
/// <param term='custom'>特定于宿主应用程序的参数数组。</param>
/// <seealso class='IDTExtensibility2' />
public void OnStartupComplete(ref Array custom)
{
}
/// <summary>实现 IDTExtensibility2 接口的 OnBeginShutdown 方法。接收正在卸载宿主应用程序的通知。</summary>
/// <param term='custom'>特定于宿主应用程序的参数数组。</param>
/// <seealso class='IDTExtensibility2' />
public void OnBeginShutdown(ref Array custom)
{
}
/// <summary>实现 IDTCommandTarget 接口的 QueryStatus 方法。此方法在更新该命令的可用性时调用</summary>
/// <param term='commandName'>要确定其状态的命令的名称。</param>
/// <param term='neededText'>该命令所需的文本。</param>
/// <param term='status'>该命令在用户界面中的状态。</param>
/// <param term='commandText'>neededText 参数所要求的文本。</param>
/// <seealso class='Exec' />
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
{
if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if (commandName == "CodeReview.Connect.CodeReview")
{
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
return;
}
}
}
/// <summary>实现 IDTCommandTarget 接口的 Exec 方法。此方法在调用该命令时调用。</summary>
/// <param term='commandName'>要执行的命令的名称。</param>
/// <param term='executeOption'>描述该命令应如何运行。</param>
/// <param term='varIn'>从调用方传递到命令处理程序的参数。</param>
/// <param term='varOut'>从命令处理程序传递到调用方的参数。</param>
/// <param term='handled'>通知调用方此命令是否已被处理。</param>
/// <seealso class='Exec' />
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "CodeReview.Connect.CodeReview")
{
handled = true;
CodeReviewForm form = new CodeReviewForm();
InitForm(ref form);
form.Show();
return;
}
}
}
private void InitForm(ref CodeReviewForm form)
{
form.FunctionName = GetFunctionName();
EnvDTE.TextSelection txtSelection = _applicationObject.ActiveDocument.Selection as EnvDTE.TextSelection;
form.OriginCode = txtSelection.Text;
form.CodeLineNumber = txtSelection.TopPoint.Line.ToString();
string projectName = "";
if (null != _applicationObject.ActiveWindow || null != _applicationObject.ActiveWindow.Project)
{
projectName = _applicationObject.ActiveWindow.Project.Name;
}
form.FileFullName = _applicationObject.ActiveDocument.FullName;
}
private string GetFunctionName()
{
ProjectItem projectItem = _applicationObject.ActiveDocument.ProjectItem;
FileCodeModel fileCodeModel = projectItem.FileCodeModel;
if (fileCodeModel.Language == CodeModelLanguageConstants.vsCMLanguageCSharp)
{
EnvDTE.TextSelection txtSelection = _applicationObject.ActiveDocument.Selection as EnvDTE.TextSelection;
CodeElement codeEmelemt = null;
try
{
codeEmelemt = fileCodeModel.CodeElementFromPoint(txtSelection.TopPoint, vsCMElement.vsCMElementFunction);
}
catch { }
if (null == codeEmelemt)
return "";
else
return codeEmelemt.Name;
}
return "";
}
}
}
Connect类中的几个方法是IDTExtensibility2, IDTCommandTarget接口的实现,执行顺序可以参见MSDN:http://msdn.microsoft.com/zh-cn/library/vstudio/ms228754.aspx。
在项目中添加一个Windows Form资源,如下:
编辑Form的CS代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CodeReview
{
public partial class CodeReviewForm : Form
{
public CodeReviewForm()
{
InitializeComponent();
}
//添加可访问性
#region
public string FunctionName
{
get { return this.txtFuncName.Text; }
set { this.txtFuncName.Text = value; }
}
public string Problem
{
get { return this.txtProblem.Text; }
set { this.txtProblem.Text = value; }
}
public string OriginCode
{
get { return this.txtOriginCode.Text; }
set { this.txtOriginCode.Text = value; }
}
public string Suggest
{
get { return this.txtSuggest.Text; }
set { this.txtSuggest.Text = value; }
}
public string Responsible
{
get { return this.txtResonsible.Text; }
set { this.txtResonsible.Text = value; }
}
public string FixTime
{
get { return this.txtFinishTime.Text; }
set { this.txtFinishTime.Text = value; }
}
/// <summary>
/// 行号
/// </summary>
public string CodeLineNumber
{ get; set; }
/// <summary>
/// 项目名称
/// </summary>
public string ProjectName
{ get; set; }
/// <summary>
/// 文件全名
/// </summary>
public string FileFullName
{
get;
set;
}
private string savePath = @"D:\\CodeReview\";
public string SavePath
{
get {
if (!System.IO.Directory.Exists(savePath))
{
System.IO.Directory.CreateDirectory(savePath);
}
return savePath;
}
}
#endregion
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
this.Dispose();
}
private void btnSave_Click(object sender, EventArgs e)
{
string content = GetRecordContent();
string path = GetSaveFileName();
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(path, true))
{
sw.Write(content);
sw.Close();
}
this.Close();
this.Dispose();
}
private string GetSaveFileName()
{
string fileName = "CodeReview"+DateTime.Now.ToString("yyyy-MM-dd")+".txt";
string savePath = SavePath + fileName;
if(!System.IO.File.Exists(savePath))
{
using (System.IO.FileStream fs = System.IO.File.Create(savePath))
{
fs.Close();
}
}
return savePath;
}
private string GetRecordContent()
{
StringBuilder sb = new StringBuilder();
sb.Append("[时间]\t" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
sb.Append(System.Environment.NewLine);
sb.Append("[组件]\t" + this.ProjectName);
sb.Append(System.Environment.NewLine);
sb.Append("[文件]\t" + this.FileFullName);
sb.Append(System.Environment.NewLine);
sb.Append("[行号]\t" + this.CodeLineNumber);
sb.Append(System.Environment.NewLine);
sb.Append("[方法]\t" + this.FunctionName);
sb.Append(System.Environment.NewLine);
sb.Append("[代码]\t" + this.OriginCode);
sb.Append(System.Environment.NewLine);
sb.Append("[问题]\t" + this.txtProblem.Text);
sb.Append(System.Environment.NewLine);
sb.Append("[建议]\t" + this.txtSuggest.Text);
sb.Append(System.Environment.NewLine);
sb.Append("[修改人]\t" +this.txtResonsible.Text);
sb.Append("\t" + "[计划完成时间]\t" + this.FixTime);
sb.Append(System.Environment.NewLine);
sb.Append("-----------------------------------------------------------------------------------------");
sb.Append(System.Environment.NewLine);
sb.Append(System.Environment.NewLine);
return sb.ToString();
}
}
}
启动VS调试,在高度的VS工具菜单中找到外接程序管理器,打开后勾选确定。如果想下次自动启用,勾选启用列
确定之后,在命令栏中会显示CodeReveiw按扭
选中代码后点击按扭会弹出Form,点击确定后会将内容保存到d:\CodeReview目录下。
这只是个原型代码,可以做很多优化和扩展,如:可以将CodeReview内容定为一个契约,可以将保存方法抽象为一个接口,再实现向数据库或WebServices等保存内容。保存方式可以用WinFrom选择,如果是本地文件,再加上一个路径选Dialog。
我尝试着响应VS EvnDET事件来更改CodeReview命令按扭的可用性,当编辑文档中选中文本内容按扭可用,当选中内容为空时按扭不可用,但在MSDN中未找到有该事件,在MSDN的论坛中得知微软未提供此事件。要实现这个功能可能要捕获mouseup事件,用hook来实现了。另外,如果要应用 EnvDET的事件,需要将事件提升为Connect的变量,在方法中给变量赋值,否则GC会回收event,无法响应事件。
最近很迷茫……
曾经年少多少事 而今皆付谈笑中!