前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实现类似“添加扩展程序…”的设计时支持

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

作者头像
明年我18
发布2019-09-18 12:49:40
4920
发布2019-09-18 12:49:40
举报
文章被收录于专栏:明年我18明年我18

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的方法实现如下:

代码语言:javascript
复制
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继承了该类,他的实现如下:

代码语言:javascript
复制
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:

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

代码语言:javascript
复制
1: if (((parent != null) && (parent.Designer != null)) && parent.DocumentDesigner.ExtenderControlHelper.ProvidesActionLists)   2:        {   3:            parent.DocumentDesigner.ExtenderControlHelper.AddActionLists(parent, actionLists);   4:        }

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

代码语言:javascript
复制
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方法定义如下:

代码语言:javascript
复制
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程序集下)的定义,如下:

代码语言:javascript
复制
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,如下:

代码语言:javascript
复制
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类的定义如下:

代码语言:javascript
复制
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类主要代码如下:

代码语言:javascript
复制
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的目的,具体实现和上面差不多,就不再写了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2010-02-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档