The Ajax Control Toolkit control library contains some extended controls. Using these extended controls, you can easily add Ajax effects to ordinary controls. For example, you can use the AutoCompleteExtender control to add automatic completion ajax effects to text boxes. Of course, this is not what this article wants to discuss.
Add the Ajax Control Toolkit to the Visual Studio 2008 toolbox, open a new aspx file, and drag a TextBox into it. At this time, something interesting happened. In the SmartTasks panel of TextBox, a link to "Add extension..." actually appeared! I tried dragging in a Button and a Panel again, and without exception, the "Add Extension..." link appeared at the bottom of the SmartTasks panel of each control.
Recently, I am planning to abstract functions such as saving, deleting, and closing pages into actions. Each action corresponds to a custom Web control. After attaching an action control to a target control (such as Button), the target control will have Functions such as saving, deleting, and closing pages. How to easily attach actions to a Button control in the WebForm designer? What I want is something like "Add extension...".
Friends who have developed custom server controls should know that if you want to add SmartTasks to the control, you need to override the ActionLists property of ControlDesigner and implement your own DesignerActionList. Obviously, a TextBox does not know the existence of AjaxControlToolkit, so "Add extension..." such a DesignerActionMethodItem is not added by it. So, does the .net framework provide some kind of interface that allows us to "dynamically inject" DesignerActionItem into other controls?
Through research on AjaxControlToolKit.dll, I found that the Designer of these extended controls is not responsible for providing the "Add extension" Action. They are only responsible for providing the extension content corresponding to the corresponding extension, so the only entry is from the webform designer of Visual studio. Come and study. Use reflector to open Microsoft Visual Studio 9.0Common7IDEMicrosoft.Web.Design.Client.dll and find the IWebSmartTasksProvider interface. This interface has a GetDesignerActionLists method. The return value of this method should be the content displayed in the SmartTasks panel. This interface has three implementation classes, DataFormDesigner, DataFormXslValueOfDesigner, and ElementDesigner. It can be inferred from the naming of these three classes that ElementDesigner should be the most commonly used implementation class. The GetDesignerActionLists method of ElementDesigner is implemented as follows:
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:
twenty one:
From the above code, you can see that the final DesignerActionListCollection is determined by GetComponentActions of the System.ComponentModel.Design.DesignerActionService class under the System.Design assembly, and Microsoft.Web.Design.WebFormDesigner under Microsoft.Web.Design.Client.dll. +WebDesignerActionService inherits this class and its implementation is as follows:
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))
twenty one: {
22: IWebDataFormElementCallback dataFormElementCallback = parent.Element.GetDataFormElementCallback();
23: if (dataFormElementCallback != null)
twenty four: {
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:
In this method, there is this paragraph:
1: if (((parent != null) && (parent.Designer != null)) && parent.DocumentDesigner.ExtenderControlHelper.ProvidesActionLists)
2: {
3: parent.DocumentDesigner.ExtenderControlHelper.AddActionLists(parent, actionLists);
4: }
It seems that the "Add extension" action is added here. Continue to look at the implementation of 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:
twenty one:
The first sentence in this method is lists.Add(new ControlExtenderActionList(element)). ControlExtenderActionList inherits System.ComponentModel.Design.DesignerActionList. Its GetSortedActionItems method is defined as follows:
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:
Now it is clear that the action "Add extension" is hard-coded in the web form designer of Visual studio. The .net framework does not provide a corresponding interface for us to add similar actions. But the effect I want is to add an "add action" action, so I can't refer to the AjaxControlToolkit method to achieve it. I should look for other methods.
Go back and re-examine the GetComponentActions method of the Microsoft.Web.Design.WebFormDesigner+WebDesignerActionService class and find the definition of the base class System.Web.UI.Design.WebFormsDesignerActionService (under the System.Design assembly), as follows:
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);
twenty one: }
twenty two: }
23: if ((actionLists.Count == 0) || ((actionLists.Count == 1) && (actionLists[0] is ControlDesigner.ControlDesignerActionList)))
twenty four: {
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:
By studying the above code, we can see that the DesignerActionListCollection is returned by the ActionLists attribute of the DesignerCommandSet service, and this service is obtained from the component's Site. As long as I write another DesignerCommandSet, and ensure that the DesignerCommandSet taken out from the Site is mine. Just write this service. Finally found the entry point, here is the specific method.
First, create a class that inherits DesignerCommandSet, as follows:
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: //Get the original DesignerActionLists of the control first
22: DesignerActionListCollection lists = _componentDesigner.ActionLists;
twenty three:
24: //Add the "Add Action" 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", "Add action...", actionCategory, true));
44: }
45: return _actions;
46: }
47:
48: public void AddAction()
49: {
50: //Add action logic, omitted
51: }
52: }
53: }
The next step is how to make the component's Site ServiceProvider return its own service. The method is to write a Site yourself and make the Component's Site an object of the SIte class you wrote.
The definition of the Site class I wrote is as follows:
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 members
14:
15: public IComponentComponent
16: {
17: get { return _site.Component; }
18: }
19:
20: public System.ComponentModel.IContainer Container
twenty one: {
22: get { return _site.Container; }
twenty three: }
twenty four:
25: public boolDesignMode
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 member
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: //Return the DesignerCommandSet written by yourself
53: service = new MyDesignerCommandSet(_designer);
54: AddService(typeof(DesignerCommandSet), service);
55: }
56: }
57: return service;
58: }
59:
60: #endregion
61:
62: #region IServiceContainer member
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: }
In the GetService method of this Site, determine the service type to be obtained. If it is a DesignerCommandSet, return the MyDesignerCommandSet you created.
The next step is how to change the component's Site into a SiteProxy written by yourself. One method is to add a custom control and change the Site of other controls in the Container in the Initialize method of the ControlDesigner of the control. You only need to drag the control into the WebForm to change the Site of other controls; another method The method is to write a vs package and capture the corresponding events of the web form designer in the package. The first approach is introduced below:
Add a new control inherited from Control, called ActionManager. This control does not need to add any functions. You only need to create a ControlDesigner for it. The main code of its ControlDesigner class is as follows:
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: //Replace the Site with existing controls
16: ProcessComponent();
17:
18: IComponentChangeService service =
19: _host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
20: if (service != null)
twenty one: {
22: service.ComponentAdded += ComponentAdded;
23: service.ComponentRemoving += ComponentRemoving;
twenty four: }
25: }
26: }
27:
28: #regionProcessComponent
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 replace Site
44:
45: /// <summary>
46: /// Replace the original Site of Component with 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: /// Restore the original site of the Component
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: //When deleting a Component, its Site property must be restored, otherwise the original Site will remain in the DesignerHost.
80: //When adding a Component with the same name in this way, a "duplicate component name" error will be reported.
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: }
At this point, as long as you drag an ActionManager control into the web form designer, you can see the "Add Action..." link on the smart task panel of other controls. However, this method requires placing an additional control in the webform designer. This control is only useful at design time and is useless at runtime. It looks strange, so the best approach is the second approach, which is to develop a vs. package, in the Initialize method of the package, register the DesignerCreated event of IDesignerEventService, and then achieve the purpose of changing the control Site through IDesignerHost and IComponentChangeService. The specific implementation is similar to the above, so I won’t write it anymore.