自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启动时自动加载控件,点击控件时通过反射触发相应的命令。
2、流程
User:终端用户(也就是各位码农)
Host:VS实例,提供全局的EnvDTE对象访问器,注册Plugin,响应IDE的各种事件(如文档打开关闭等)
Plugin:基于VsSharp开发的插件(此处为避免与EnvDTE.AddIn重名,命名为Plugin)
由此引出VsSharp的职责
1、对象设计
1.1 基于上述职责定义,抽象出如下对象:
CommandBarAccessor的行为:
public interface ICommandBarAccessor { void AddControl(CommandControl control); void ResetControl(CommandControl control); void EnableControls(IEnumerable<string> ids ,bool enabled); void Delete(); }
1.2 命令接口
public interface ICommand { void Execute(object arg = null); }
命令类型:
public enum CommandActionType { Menu, Program, Window, Dialog }
1.3 命令控件描述
主要有两种控件类型:
抽象类CommandControl:CommandMenu和CommandButton的父类,描述控件的ID、文本、图标、命令类型、位置、所属父控件等属性。
以下代码段为CommandControl的全部属性。
其中,
ClassName为供反射用的动作类型名称,当CommandActionType为Program时,要求该类型实现了ICommand接口。
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为配置文件中定义的供反射用的子菜单生成器类型,用于启动时根据特定数据源自动生成。
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、类图
调用关系 :
http://vssharp.codeplex.com/
---------------------------------------------------
下篇将以一个实例来讲解框架的使用,敬请期待。