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

王朝学院·作者佚名  2010-02-26
窄屏简体版  字體: 小  |  中  |  大  |  超大  

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的目的,具体实现和上面差不多,就不再写了。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航