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/

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

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

  • 相关阅读:
    Get distinct count of rows in the DataSet
    单引号双引号的html转义符
    PETS Public English Test System
    Code 39 basics (39条形码原理)
    Index was outside the bounds of the array ,LocalReport.Render
    Thread was being aborted Errors
    Reportviewer Error: ASP.NET session has expired
    ReportDataSource 值不在预期的范围内
    .NET/FCL 2.0在Serialization方面的增强
    Perl像C一样强大,像awk、sed等脚本描述语言一样方便。
  • 原文地址:https://www.cnblogs.com/cnsharp/p/4580965.html
Copyright © 2011-2022 走看看