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啟動時自動加載控件,點擊控件時通過反射觸發相應的命令。

2、流程

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接口。

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、類圖

 

 調用關系 :

  • Connect對象啟動時加載CommandConfig,生成一個Plugin對象傳給CommandManager,並向Host.Instance注冊;CommandManager加載CommandConfig描述的所有控件
  • Connect.OnConnection方法調用CommandManager.Load方法
  • Connect.Exec方法調用CommandManager.Execute方法
  • Connect.OnDisconnection方法調用CommandManager.Disconnect方法

 

四、源代碼

http://vssharp.codeplex.com/

 

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

下篇將以一個實例來講解框架的使用,敬請期待。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM