zoukankan      html  css  js  c++  java
  • 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/

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

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

  • 相关阅读:
    WPF 使用Code创建Canvas,StackPanel,DockPanel。Grid,Border,UniformGrid,ViewBox,WrapPanel等
    myeclipse6.5安装maven2插件教程 (Maven+MyEclipse6.5)
    myeclipse使用maven教程
    【技术贴】如何汉化 myeclipse 中的svn插件
    【技术贴】小米利用Connectify共享无线网卡连接教程
    Myeclipse 自动生成 javadoc 教程
    MyEclipse6.5配置Tomcat7.x
    maven构建项目自动部署到tomcat中遇到的各种sb问题总结
    吐槽之程序猿
    html5 css3构造的漂亮表格
  • 原文地址:https://www.cnblogs.com/cnsharp/p/4580965.html
Copyright © 2011-2022 走看看