转载

VsSharp:一个VS扩展开发框架(上)

上篇:设计

一、引子

自2008年起开发SSMS插件SqlSharp(er)的过程中,有一天发现多数代码都大同小异,就像这样。

Commands2 commands = (Commands2)_applicationObject.Commands; string toolsMenuName = "Tools"; //Place the command on the tools menu. //Find the MenuBar command bar, which is the top-level command bar holding all the main menu items: Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"]; //Find the Tools command bar on the MenuBar command bar: CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName]; CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl; //This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in, //  just make sure you also update the QueryStatus/Exec method to include the new command names. try {  //Add a command to the Commands collection:  // add + (int)vsCommandStatus.vsCommandStatusEnabled if we want the default state to be enabled  Command command = commands.AddNamedCommand2(_addInInstance, "FormatSQL", "Format SQL", "Format SQL", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);  //Add a control for the command to the tools menu:  if ((command != null) && (toolsPopup != null))  {   command.AddControl(toolsPopup.CommandBar, 1);  } } catch (System.ArgumentException) {  //If we are here, then the exception is probably because a command with that name  //  already exists. If so there is no need to recreate the command and we can   //  safely ignore the exception. } 

于是萌生出开发一个框架的想法。

于是有了一个叫SsmsSharp的框架,后正式命名为SqlSharp发布到了CodePlex上。

与此同时,将操纵EnvDTE的代码与SSMS Objects的代码分离,操纵EnvDTE的代码就形成了本篇要说的VsSharp。

后来,当我正式使用VsSharp开发VS扩展时,又引出一些新问题如源代码签出、一个VS搭载多个扩展,解决这些问题后,VsSharp开始成熟起来。

二、设计思路

1、目标

应用Command模式,定义每个控件的行为。将一个个控件的属性与行为集合在一个配置文件中,在VS启动时自动加载控件,点击控件时通过反射触发相应的命令。

VsSharp:一个VS扩展开发框架(上)

2、流程

VsSharp:一个VS扩展开发框架(上)

User:终端用户(也就是各位码农)

Host:VS实例,提供全局的EnvDTE对象访问器,注册Plugin,响应IDE的各种事件(如文档打开关闭等)

Plugin:基于VsSharp开发的插件(此处为避免与EnvDTE.AddIn重名,命名为Plugin)

由此引出VsSharp的职责

  1. 负责配置的加载
  2. 向VS注册控件
  3. 响应用户的点击及其他事件

三、概要设计

1、对象设计

1.1 基于上述职责定义,抽象出如下对象:

  • CommandConfig:负责命令与控件的配置描述
  • CommandManager:负责配置的加载,和与VS的交互
  • CommandBarAccessor:与VS直接交互的对象,实现ICommandBarAccessor接口,主要是为了隔离VS版本的差异
  • Host:宿主,单例,表示当前操作的VS实例;CommandAccessor通过它与EnvDTE交互
  • PlugIn:主要属性为CommandConfig和Connect入口的Assembly

CommandBarAccessor的行为:

public interface ICommandBarAccessor     {         void AddControl(CommandControl control);         void ResetControl(CommandControl control);         void EnableControls(IEnumerable<string> ids ,bool enabled);         void Delete();     }
  • AddContro:添加一个控件
  • ResetControl:重置控件,比如某控件的子菜单可以依赖于特定的配置或数据源,当配置或数据源发生变化时,需要重新加载控件
  • EnableControl:启用(禁用)控件,比如某些控件是用于操作文档的,当有文档打开时才启用
  • Delete:删除控件,当VS退出时执行Disconnect方法时触发

1.2 命令接口

public interface ICommand     {         void Execute(object arg = null);     }

命令类型:

public enum CommandActionType     {         Menu,         Program,         Window,         Dialog     }
  • Menu:缺省类型,无任何行为
  • Program:执行一段程序
  • Window:打开一个窗体
  • Dialog:打开一个模态窗体

1.3 命令控件描述

主要有两种控件类型:

  • CommandMenu:包括主菜单栏的自定义菜单、上下文菜单,其下级可以有子菜单
  • CommandButton:主要是插入ToolStrip的ToolStripButton,其下级也可以有子菜单

抽象类CommandControl:CommandMenu和CommandButton的父类,描述控件的ID、文本、图标、命令类型、位置、所属父控件等属性。

以下代码段为CommandControl的全部属性。

其中,

ClassName为供反射用的动作类型名称,当CommandActionType为Program时,要求该类型实现了ICommand接口。

VsSharp:一个VS扩展开发框架(上)
public abstract class CommandControl     {  private Form _form;  private int _position;  private Image _image;  private string _arg;  private ICommand _command;  /// <summary>  /// Constructor  /// </summary>  protected CommandControl()  {      CommandActionType = CommandActionType.Menu;      Position = 1;  }  /// <summary>  /// Id,as while as the command Name  /// </summary>  [XmlAttribute("id")]  public string Id { get; set; }  /// <summary>  /// Text  /// </summary>  [XmlAttribute("text")]  public string Text { get; set; }  /// <summary>  /// Tooltip text  /// </summary>  [XmlAttribute("tooltip")]  public string Tooltip { get; set; }  /// <summary>  /// Office style icon face id  /// </summary>  [XmlAttribute("faceId")]  public int FaceId { get; set; }  /// <summary>  ///   Relative position in the parent control,can be minus  /// </summary>  /// <remarks>  /// 相对于父控件Child总数n而言,大于等于0则放在末尾n+1的位置,为负数则放在倒数第n-Position的位置  /// </remarks>  [XmlAttribute("position")]  public int Position  {      get { return _position; }      set      {   if (value >= 0)       value = 1;   _position = value;      }  }  /// <summary>  /// Picture id in ResourceManager  /// </summary>  [XmlAttribute("picture")]  public string Picture { get; set; }  [XmlIgnore]  public StdPicture StdPicture  {      get      {   if (!String.IsNullOrEmpty(Picture) && Plugin != null && Plugin.ResourceManager != null)   {       return Plugin.ResourceManager.LoadPicture(Picture);   }   return null;      }  }  /// <summary>  /// Image instance from ResourceManager  /// </summary>  [XmlIgnore]  public Image Image  {      get      {   if (_image == null && !string.IsNullOrEmpty(Picture) && Picture.Trim().Length > 0 && Plugin != null && Plugin.ResourceManager != null)   {       _image = Plugin.ResourceManager.LoadBitmap(Picture);   }   return _image;      }      set      {   _image = value;      }  }  /// <summary>  /// Action class type name  /// </summary>  [XmlAttribute("class")]  public string ClassName { get; set; }  /// <summary>  /// Action type  /// </summary>  [XmlAttribute("type")]  public CommandActionType CommandActionType { get; set; }  /// <summary>  /// Parent control name that the control attach to  /// </summary>  [XmlAttribute("attachTo")]  public string AttachTo { get; set; }  //[XmlAttribute("hotKey")]  //public string HotKey { get; set; }  /// <summary>  /// begin group,insert a bar in context menu if set True  /// </summary>  [XmlAttribute("beginGroup")]  public bool BeginGroup { get; set; }  /// <summary>  /// Command instance of <see cref="ClassName"/>  /// </summary>  [XmlIgnore]  public ICommand Command  {      get { return _command ?? (_command = LoadInstance(ClassName) as ICommand); }      set { _command = value; }  }  /// <summary>  /// <see cref="Plugin"/> which the control attach to  /// </summary>  [XmlIgnore]  public Plugin Plugin { get; set; }  /// <summary>  /// Argument for <see cref="ICommand"/> execution  /// </summary>  [XmlAttribute("arg")]  public string Arg  {      get { return _arg; }      set      {   _arg = value;   Tag = _arg;      }  }  /// <summary>  /// <see cref="DependentItems"/> name for making the control  enabled or disabled  /// </summary>  [XmlAttribute("dependOn")]  public string DependOn { get; set; }    [XmlIgnore]  public DependentItems DependentItems {        get        {     return string.IsNullOrEmpty(DependOn) || DependOn.Trim().Length == 0         ? DependentItems.None         : (DependentItems)Enum.Parse(typeof(DependentItems), DependOn);        } }  /// <summary>    /// Argument for <see cref="ICommand"/> execution,only be assgined by programming  /// </summary>  [XmlIgnore]  public object Tag { get; set; }  public override string ToString()  {      return Text;  }  /// <summary>  /// execute action  /// </summary>  public virtual void Execute()  {      var arg = Arg ?? Tag;      switch (CommandActionType)      {   case CommandActionType.Program:       if (Command != null)       {    Command.Execute(arg);       }       break;   case CommandActionType.Window:       var window = GetForm();       window.Show();       break;   case CommandActionType.Dialog:       var dialog = GetForm();       dialog.ShowDialog();       break;      }  }  /// <summary>  /// load an instance  /// </summary>  /// <param name="typeName"></param>  /// <returns></returns>  public object LoadInstance(string typeName)  {      if (typeName.Contains(","))      {   var arr = typeName.Split(',');   if (arr.Length < 2)       return null;   var assemblyName = arr[1];   try   {       var assembly = Assembly.Load(assemblyName);       return assembly.CreateInstance(arr[0]);   }   catch   {       var file = Path.Combine(Plugin.Location, assemblyName + ".dll");       if (File.Exists(file))       {    var assembly = Assembly.LoadFile(file);    return assembly.CreateInstance(arr[0]);       }   }      }      return Plugin.Assembly.CreateInstance(typeName);  }  private Form GetForm()  {      if (_form != null && !_form.IsDisposed)   return _form;      _form = (Form)LoadInstance(ClassName);      return _form;  }     } 
View Code

CommandMenu继承CommandControl,特有子菜单相关属性。

其中SubMenus属性可在编程时操纵,SubGeneratorType为配置文件中定义的供反射用的子菜单生成器类型,用于启动时根据特定数据源自动生成。

VsSharp:一个VS扩展开发框架(上)
public class CommandMenu : CommandControl {  private List<CommandMenu> _subMenus;  public CommandMenu()  {   _subMenus = new List<CommandMenu>();  }  [XmlElement("menu")]  public List<CommandMenu> SubMenus  {   get   {    if (_subMenus.Count == 0 && !string.IsNullOrEmpty(SubGeneratorType))    {     LoadSubMenus();    }    return _subMenus;   }   set { _subMenus = value; }  }  [XmlAttribute("sgt")]  public string SubGeneratorType { get; set; }  protected virtual IEnumerable<CommandMenu> GenerateSubMenus()  {   if (string.IsNullOrEmpty(SubGeneratorType) || SubGeneratorType.Trim().Length == 0)    return null;   var gen = LoadInstance(SubGeneratorType) as ICommandMenuGenerator;   if (gen == null)    return null;   return gen.Generate();  }  public virtual void LoadSubMenus()  {   if (GenerateSubMenus() == null)    return;   _subMenus = GenerateSubMenus().ToList();  } } 
View Code

2、类图

VsSharp:一个VS扩展开发框架(上)

调用关系 :

  • Connect对象启动时加载CommandConfig,生成一个Plugin对象传给CommandManager,并向Host.Instance注册;CommandManager加载CommandConfig描述的所有控件
  • Connect.OnConnection方法调用CommandManager.Load方法
  • Connect.Exec方法调用CommandManager.Execute方法
  • Connect.OnDisconnection方法调用CommandManager.Disconnect方法

四、源代码

http://vssharp.codeplex.com/

---------------------------------------------------

下篇将以一个实例来讲解框架的使用,敬请期待。

正文到此结束
Loading...