自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. }
因而萌生出開發一個框架的想法。app
因而有了一個叫SsmsSharp的框架,後正式命名爲SqlSharp發佈到了CodePlex上。框架
與此同時,將操縱EnvDTE的代碼與SSMS Objects的代碼分離,操縱EnvDTE的代碼就造成了本篇要說的VsSharp。ssh
後來,當我正式使用VsSharp開發VS擴展時,又引出一些新問題如源代碼簽出、一個VS搭載多個擴展,解決這些問題後,VsSharp開始成熟起來。ide
一、目標spa
應用Command模式,定義每一個控件的行爲。將一個個控件的屬性與行爲集合在一個配置文件中,在VS啓動時自動加載控件,點擊控件時經過反射觸發相應的命令。插件
二、流程設計
User:終端用戶(也就是各位碼農)3d
Host:VS實例,提供全局的EnvDTE對象訪問器,註冊Plugin,響應IDE的各類事件(如文檔打開關閉等)code
Plugin:基於VsSharp開發的插件(此處爲避免與EnvDTE.AddIn重名,命名爲Plugin)
由此引出VsSharp的職責
一、對象設計
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; } }
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(); } }
二、類圖
調用關係 :
---------------------------------------------------
下篇將以一個實例來說解框架的使用,敬請期待。