点击这里给我发消息 点击这里给我发消息

实现类似“添加扩展程序…”的设计时支持

添加时间:2013-12-6
    相关阅读: 设计 开发 页面 程序 链接 制作

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个实现类,DataFormDesigner、DataFormXslValueOfDesigner、ElementDesigner。从这三个类的命名上可以推断,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的目的,具体实现和上面差不多,就不再写了。

 

咨询热线:020-85648757 85648755 85648616 0755-27912581 客服:020-85648756 0755-27912581 业务传真:020-32579052
广州市网景网络科技有限公司 Copyright◎2003-2008 Veelink.com. All Rights Reserved.
广州商务地址:广东省广州市黄埔大道中203号(海景园区)海景花园C栋501室
= 深圳商务地址:深圳市宝源路华丰宝源大厦606
研发中心:广东广州市天河软件园海景园区 粤ICP备05103322号 工商注册