zoukankan      html  css  js  c++  java
  • 实现类似“添加扩展程序…”的设计时支持

        Ajax Control Toolkit这个控件库内包含一些扩展控件,利用这些扩展控件,可以非常方便的为普通的控件添加Ajax效果,例如,利用AutoCompleteExtender控件,可以为文本框添加自动完成的ajax效果。当然,这并不是本文想讨论的内容。

        将Ajax Control Toolkit加入到Visual Studio 2008的工具箱中,并打开一个新的aspx文件,向里面拖入一个TextBox。这时,有趣的事情发生了,在TextBox的SmartTasks面板里,竟然出现了一个“添加扩展程序…”的链接!我又试着拖入一个Button,一个Panel,无一例外的,每个控件的SmartTasks面板的底部都出现了“添加扩展程序…”的链接。

        最近我正打算把保存、删除、关闭页面等功能抽象成动作,每一种动作对应一个自定义的Web控件,将某个动作控件附加到目标控件(例如Button)上面之后,目标控件就拥有了诸如保存、删除、关闭页面的功能。如何在WebForm设计器里为一个Button控件方便地附加动作?我想要的正是类似“添加扩展程序…”这样的效果。

        开发过自定义服务器控件的朋友应该知道,如果想给控件添加SmartTasks,需要重写ControlDesigner的ActionLists属性,并实现自己的DesignerActionList。显然,一个TextBox并不知道AjaxControlToolkit的存在,所以“添加扩展程序…”这么一个DesignerActionMethodItem并不是它加进来的。那么,.net framework是否提供了某种接口,可以让我们为别的控件“动态的注入”DesignerActionItem呢?

        通过对AjaxControlToolKit.dll的研究,我发现这些扩展控件的Designer并不负责提供“添加扩展程序”这个Action,他们只负责提供相应扩展程序对应的扩展内容,所以只能从Visual studio的webform designer作为入口来研究。用reflector打开Microsoft Visual Studio 9.0\Common7\IDE\Microsoft.Web.Design.Client.dll,找到了IWebSmartTasksProvider接口,该接口有一个GetDesignerActionLists的方法,这个方法的返回值应该就是SmartTasks面板里显示的内容了。这个接口有3个实现类,DataFormDesignerDataFormXslValueOfDesignerElementDesigner。从这三个类的命名上可以推断,ElementDesigner应该是用的最多的实现类了。ElementDesigner的GetDesignerActionLists的方法实现如下:

       1: DesignerActionListCollection IWebSmartTasksProvider.GetDesignerActionLists()
       2: {
       3:     DesignerActionListCollection componentActions = null;
       4:     if (this.Designer != null)
       5:     {
       6:         DesignerActionService service = (DesignerActionService) base.DesignerHost.GetService(typeof(DesignerActionService));
       7:         if (service != null)
       8:         {
       9:             componentActions = service.GetComponentActions(this.Designer.Component);
      10:         }
      11:     }
      12:     if (componentActions == null)
      13:     {
      14:         componentActions = new DesignerActionListCollection();
      15:     }
      16:     return componentActions;
      17: }
      18:  
      19:  
      20:  
      21:  

        从上面代码里可以看到最终的DesignerActionListCollection是由System.Design程序集下的System.ComponentModel.Design.DesignerActionService类的GetComponentActions决定的,Microsoft.Web.Design.Client.dll下的Microsoft.Web.Design.WebFormDesigner+WebDesignerActionService继承了该类,他的实现如下:

       1: protected override void GetComponentDesignerActions(IComponent component, DesignerActionListCollection actionLists)
       2: {
       3:     Control control = component as Control;
       4:     ElementDesigner parent = null;
       5:     if (control != null)
       6:     {
       7:         parent = ElementDesigner.GetElementDesigner(control);
       8:     }
       9:     if ((parent == null) || !parent.InTemplateMode)
      10:     {
      11:         base.GetComponentDesignerActions(component, actionLists);
      12:         if ((parent != null) && (parent.Designer != null))
      13:         {
      14:             ControlDesigner designer = parent.Designer as ControlDesigner;
      15:             if ((designer != null) && (designer.AutoFormats.Count > 0))
      16:             {
      17:                 actionLists.Insert(0, new AutoFormatActionList(parent));
      18:             }
      19:         }
      20:         if ((parent != null) && (parent.Element != null))
      21:         {
      22:             IWebDataFormElementCallback dataFormElementCallback = parent.Element.GetDataFormElementCallback();
      23:             if (dataFormElementCallback != null)
      24:             {
      25:                 DataFormElementActionList list = new DataFormElementActionList(parent, parent.Control, dataFormElementCallback);
      26:                 actionLists.Add(list);
      27:                 DataFormElementActionList.ModifyActionListsForListControl(actionLists, list);
      28:             }
      29:         }
      30:         if (((parent != null) && (parent.Designer != null)) && parent.DocumentDesigner.ExtenderControlHelper.ProvidesActionLists)
      31:         {
      32:             parent.DocumentDesigner.ExtenderControlHelper.AddActionLists(parent, actionLists);
      33:         }
      34:     }
      35:     if ((parent != null) && (parent.TemplateEditingUI != null))
      36:     {
      37:         actionLists.Add(new TemplateEditingActionList(parent.TemplateEditingUI, parent.Element));
      38:     }
      39: }
      40:  
      41:  
      42:  
      43:  

        这个方法里,有这么一段:

       1: if (((parent != null) && (parent.Designer != null)) && parent.DocumentDesigner.ExtenderControlHelper.ProvidesActionLists)
       2:        {
       3:            parent.DocumentDesigner.ExtenderControlHelper.AddActionLists(parent, actionLists);
       4:        }

        看来“添加扩展程序”这个action就是在这里加进去的了。继续查看ExtenderControlHelper.AddActionLists的实现:

       1: public void AddActionLists(ElementDesigner element, DesignerActionListCollection lists)
       2: {
       3:     lists.Add(new ControlExtenderActionList(element));
       4:     ExtenderControl component = element.Designer.Component as ExtenderControl;
       5:     Control control = element.Designer.Component as Control;
       6:     if ((component == null) && (control != null))
       7:     {
       8:         IExtenderInformationService service = (IExtenderInformationService) control.Site.GetService(typeof(IExtenderInformationService));
       9:         if (service != null)
      10:         {
      11:             foreach (Control control3 in service.GetAppliedExtenders(control))
      12:             {
      13:                 lists.Add(new HoistedExtenderActionList(element.Designer, control3));
      14:             }
      15:         }
      16:     }
      17: }
      18:  
      19:  
      20:  
      21:  

        这个方法里的第一句是lists.Add(new ControlExtenderActionList(element)),ControlExtenderActionList继承了System.ComponentModel.Design.DesignerActionList,他的GetSortedActionItems方法定义如下:

       1: public override DesignerActionItemCollection GetSortedActionItems()
       2: {
       3:     Control component = (Control) this._htmlDesigner.Component;
       4:     DesignerActionItemCollection items = new DesignerActionItemCollection();
       5:     IExtenderInformationService service = (IExtenderInformationService) component.Site.GetService(typeof(IExtenderInformationService));
       6:     string category = SR.GetString(SR.Ids.SmartTasksLabelExtenderSection, CultureInfo.CurrentUICulture);
       7:     if (service.IsControlExtendible(component))
       8:     {
       9:         string displayName = SR.GetString(SR.Ids.SmartTasksAddExtender, CultureInfo.CurrentUICulture);
      10:         items.Add(new DesignerActionMethodItem(this, "AddExtender", displayName, category, true));
      11:     }
      12:     if (service.IsControlExtended(component))
      13:     {
      14:         string str3 = SR.GetString(SR.Ids.SmartTasksRemoveExtender, CultureInfo.CurrentUICulture);
      15:         items.Add(new DesignerActionMethodItem(this, "RemoveExtender", str3, category, true));
      16:     }
      17:     return items;
      18: }
      19:  

        这下清楚了,“添加扩展程序”这个action,是在Visual studio的web form设计器里,写死进去的,.net framework并没有提供相应接口来供我们添加类似的action。但是我想要的效果是增加一个“添加动作”的action,所以我不能参考AjaxControlToolkit的方法去实现,应该要寻找别的方法。

        回过头来,重新查看Microsoft.Web.Design.WebFormDesigner+WebDesignerActionService类的GetComponentActions方法,找到基类System.Web.UI.Design.WebFormsDesignerActionService(在System.Design程序集下)的定义,如下:

       1: protected override void GetComponentDesignerActions(IComponent component, DesignerActionListCollection actionLists)
       2: {
       3:     if (component == null)
       4:     {
       5:         throw new ArgumentNullException("component");
       6:     }
       7:     if (actionLists == null)
       8:     {
       9:         throw new ArgumentNullException("actionLists");
      10:     }
      11:     IServiceContainer site = component.Site as IServiceContainer;
      12:     if (site != null)
      13:     {
      14:         DesignerCommandSet service = (DesignerCommandSet) site.GetService(typeof(DesignerCommandSet));
      15:         if (service != null)
      16:         {
      17:             DesignerActionListCollection lists = service.ActionLists;
      18:             if (lists != null)
      19:             {
      20:                 actionLists.AddRange(lists);
      21:             }
      22:         }
      23:         if ((actionLists.Count == 0) || ((actionLists.Count == 1) && (actionLists[0] is ControlDesigner.ControlDesignerActionList)))
      24:         {
      25:             DesignerVerbCollection verbs = service.Verbs;
      26:             if ((verbs != null) && (verbs.Count != 0))
      27:             {
      28:                 DesignerVerb[] array = new DesignerVerb[verbs.Count];
      29:                 verbs.CopyTo(array, 0);
      30:                 actionLists.Add(new DesignerActionVerbList(array));
      31:             }
      32:         }
      33:     }
      34: }
      35:  
      36:  
      37:  
      38:  

        通过研究上述代码,可以看到DesignerActionListCollection是由DesignerCommandSet这个service的ActionLists属性负责返回的,而这个service是从component的Site里面取得的,只要我另外写一个DesignerCommandSet,并且保证从Site里面取出的DesignerCommandSet是我写的这个service就可以了。终于找到了切入点,下面是具体做法。

        首先,创建一个类继承DesignerCommandSet,如下:

       1: public class MyDesignerCommandSet : DesignerCommandSet
       2:     {
       3:         private ComponentDesigner _componentDesigner;
       4:  
       5:         public MyDesignerCommandSet(ComponentDesigner componentDesigner)
       6:         {
       7:             _componentDesigner = componentDesigner;
       8:         }
       9:  
      10:         public override ICollection GetCommands(string name)
      11:         {
      12:             if (name.Equals("ActionLists"))
      13:             {
      14:                 return GetActionLists();
      15:             }
      16:             return base.GetCommands(name);
      17:         }
      18:  
      19:         private DesignerActionListCollection GetActionLists()
      20:         {
      21:             //先取得控件原有的DesignerActionLists
      22:             DesignerActionListCollection lists = _componentDesigner.ActionLists;
      23:             
      24:             //增加“添加动作”这个DesignerActionList
      25:             lists.Add(new ActionList(_componentDesigner));
      26:             return lists;
      27:         }
      28:  
      29:         internal class ActionList : DesignerActionList
      30:         {
      31:             private DesignerActionItemCollection _actions;
      32:  
      33:             public ActionList(IDesigner designer)
      34:                 : base(designer.Component)
      35:             {
      36:             }
      37:             public override DesignerActionItemCollection GetSortedActionItems()
      38:             {
      39:                 if (_actions == null)
      40:                 {
      41:                     const string actionCategory = "Actions";
      42:                     _actions = new DesignerActionItemCollection();
      43:                     _actions.Add(new DesignerActionMethodItem(this, "AddAction", "添加动作...", actionCategory, true));
      44:                 }
      45:                 return _actions;
      46:             }
      47:  
      48:             public void AddAction()
      49:             {
      50:                 //添加动作的逻辑,略
      51:             }
      52:         }
      53:     }

        下一步就是如何使component的Site这个ServiceProvider返回自己的这个service。方法是自己写一个Site,并使Component的Site变成自己写的SIte类的对象。

        自己写的Site类的定义如下:

       1: public class SiteProxy : ISite, IServiceContainer
       2:     {
       3:         private ISite _site;
       4:         private ComponentDesigner _designer;
       5:  
       6:         public SiteProxy(ISite site, ComponentDesigner designer)
       7:         {
       8:             _site = site;
       9:             _designer = designer;
      10:  
      11:         }
      12:  
      13:         #region ISite 成员
      14:  
      15:         public IComponent Component
      16:         {
      17:             get { return _site.Component; }
      18:         }
      19:  
      20:         public System.ComponentModel.IContainer Container
      21:         {
      22:             get { return _site.Container; }
      23:         }
      24:  
      25:         public bool DesignMode
      26:         {
      27:             get { return _site.DesignMode; }
      28:         }
      29:  
      30:         public string Name
      31:         {
      32:             get { return _site.Name; }
      33:             set { _site.Name = value; }
      34:         }
      35:  
      36:         #endregion
      37:  
      38:         #region IServiceProvider 成员
      39:  
      40:         public object GetService(Type serviceType)
      41:         {
      42:             object service = _site.GetService(serviceType);
      43:  
      44:             if (serviceType == typeof(DesignerCommandSet) && !(_designer.Component is ExtenderControl))
      45:             {
      46:                 if (service == null || !(service is MyDesignerCommandSet))
      47:                 {
      48:                     if (service != null)
      49:                     {
      50:                         RemoveService(typeof(DesignerCommandSet));
      51:                     }
      52:                     //返回自己写的DesignerCommandSet
      53:                     service = new MyDesignerCommandSet(_designer);
      54:                     AddService(typeof(DesignerCommandSet), service);
      55:                 }
      56:             }
      57:             return service;
      58:         }
      59:  
      60:         #endregion
      61:  
      62:         #region IServiceContainer 成员
      63:  
      64:         public void AddService(Type serviceType, ServiceCreatorCallback callback, bool promote)
      65:         {
      66:             (_site as IServiceContainer).AddService(serviceType, callback, promote);
      67:         }
      68:  
      69:         public void AddService(Type serviceType, ServiceCreatorCallback callback)
      70:         {
      71:             (_site as IServiceContainer).AddService(serviceType, callback);
      72:         }
      73:  
      74:         public void AddService(Type serviceType, object serviceInstance, bool promote)
      75:         {
      76:             (_site as IServiceContainer).AddService(serviceType, serviceInstance, promote);
      77:         }
      78:  
      79:         public void AddService(Type serviceType, object serviceInstance)
      80:         {
      81:             (_site as IServiceContainer).AddService(serviceType, serviceInstance);
      82:         }
      83:  
      84:         public void RemoveService(Type serviceType, bool promote)
      85:         {
      86:             (_site as IServiceContainer).RemoveService(serviceType, promote);
      87:         }
      88:  
      89:         public void RemoveService(Type serviceType)
      90:         {
      91:             (_site as IServiceContainer).RemoveService(serviceType);
      92:         }
      93:  
      94:         #endregion
      95:     }

        在这个Site的GetService方法中,判断要get的service类型,如果是DesignerCommandSet,就返回自己创建的MyDesignerCommandSet。

        下一步是如何使component的Site变成自己写的SiteProxy。一种方法是新增一种自定义控件,在该控件的ControlDesigner的Initialize方法中改变Container中其他控件的Site,只需要向WebForm中拖入该控件,就可以改变其他控件的Site;另外一种方法是写一个vs package,在package中捕获web form designer的相应事件。下面介绍第一种做法:

        新增一个继承自Control的控件,叫做ActionManager,这个控件不用添加任何功能,只需要为它制作ControlDesigner。它的ControlDesigner类主要代码如下:

       1: public class ActionManagerDesigner : ControlDesigner
       2:     {
       3:         private IDesignerHost _host;
       4:         private IDictionary<IComponent, ISite> _components;
       5:  
       6:         public override void Initialize(IComponent component)
       7:         {
       8:             base.Initialize(component);
       9:  
      10:             _components = new Dictionary<IComponent, ISite>();
      11:  
      12:             _host = GetService(typeof(IDesignerHost)) as IDesignerHost;
      13:             if (_host != null)
      14:             {
      15:                 //替换已有控件的Site
      16:                 ProcessComponent();
      17:  
      18:                 IComponentChangeService service =
      19:                     _host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
      20:                 if (service != null)
      21:                 {
      22:                     service.ComponentAdded += ComponentAdded;
      23:                     service.ComponentRemoving += ComponentRemoving;
      24:                 }
      25:             }
      26:         }
      27:  
      28:         #region ProcessComponent
      29:  
      30:         private void ProcessComponent()
      31:         {
      32:             ComponentCollection components = _host.Container.Components;
      33:             foreach (IComponent component in components)
      34:             {
      35:                 if (component is ActionControl)
      36:                     continue;
      37:                 ProcessComponentSite(component);
      38:             }
      39:         }
      40:  
      41:         #endregion
      42:  
      43:         #region 替换Site
      44:  
      45:         /// <summary>
      46:         /// 替换Component原来的Site,换成SiteProxy
      47:         /// </summary>
      48:         private void ProcessComponentSite(IComponent component)
      49:         {
      50:             ComponentDesigner designer = _host.GetDesigner(component) as ComponentDesigner;
      51:             _components[component] = component.Site;
      52:             component.Site = new SiteProxy(component.Site, designer);
      53:         }
      54:  
      55:         /// <summary>
      56:         /// 恢复Component原来的site
      57:         /// </summary>
      58:         /// <param name="component"></param>
      59:         private void RestoreComponentSite(IComponent component)
      60:         {
      61:             if (_components.ContainsKey(component))
      62:             {
      63:                 ISite site = _components[component];
      64:                 component.Site = site;
      65:                 _components.Remove(component);
      66:             }
      67:         }
      68:  
      69:         #endregion
      70:  
      71:         #region on Component Add, remove, change
      72:         
      73:         private void ComponentRemoving(object sender, ComponentEventArgs e)
      74:         {
      75:             if (e.Component is ActionControl)
      76:             {
      77:                 return;
      78:             }
      79:             //在删除Component的时候,要把他的Site属性还原回去,否则DesignerHost中还会保留原来的Site,
      80:             //这样再添加同名的Component的时候,会报“重复的组件名称”错误
      81:             RestoreComponentSite(e.Component);
      82:         }
      83:        
      84:  
      85:         private void ComponentAdded(object sender, ComponentEventArgs e)
      86:         {
      87:             if (e.Component is ActionControl)
      88:             {
      89:                 return;
      90:             }
      91:             ProcessComponentSite(e.Component);
      92:         }
      93:  
      94:         #endregion
      95:  
      96:         #region dispose
      97:  
      98:         protected override void Dispose(bool disposing)
      99:         {
     100:             if (_host != null)
     101:             {
     102:                 IComponentChangeService service =
     103:                     _host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
     104:                 if (service != null)
     105:                 {
     106:                     service.ComponentAdded -= ComponentAdded;
     107:                     service.ComponentRemoving -= ComponentRemoving;
     108:                 }
     109:             }
     110:             base.Dispose(disposing);
     111:         }
     112:  
     113:         #endregion
     114:     }

       至此,只要把一个ActionManager控件拖入到web form designer中,就可以在其他控件的smart task面板上看到“添加动作…”这个链接了。但是这种方式需要在webform designer中放入额外的一个控件,该控件只在设计时有用,在运行时则无用,看起来比较奇怪,所以最好的做法是第二种做法,即开发一个vs package,在package的Initialize方法中,注册IDesignerEventService的DesignerCreated事件,进而通过IDesignerHost和IComponentChangeService达到更改控件Site的目的,具体实现和上面差不多,就不再写了。

  • 相关阅读:
    python爬取京东菜单
    There is no Action mapped for namespace [/] and action name [] associated with context path [/ch_05_ActionAnnotation].
    Java基础学习,一些零散的笔记之抽象类与接口
    java基础学习,一些零散的笔记之内部类
    Java基础学习,一些零散的笔记之Java的包
    java native 关键字
    整理一下Java动态编译Java代码,并在加载到内存中然后执行类中方法的api的介绍
    有关jdbc驱动的问题,java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
    学习js,遇到坑爹的combobox的text值的清空问题
    怎样得到一个类中的所定义的变量的变量名
  • 原文地址:https://www.cnblogs.com/default/p/1672207.html
Copyright © 2011-2022 走看看