Ajax Control Toolkit 컨트롤 라이브러리에는 일부 확장 컨트롤이 포함되어 있어 일반 컨트롤에 Ajax 효과를 쉽게 추가할 수 있습니다. 예를 들어 AutoCompleteExtender 컨트롤을 사용하면 텍스트 상자에 자동 완성 Ajax 효과를 추가할 수 있습니다. 물론 이 글이 다루고자 하는 내용은 이것이 아니다.
Visual Studio 2008 도구 상자에 Ajax Control Toolkit을 추가하고 새 aspx 파일을 열고 TextBox를 끌어다 놓습니다. 이때 TextBox의 SmartTasks 패널에 실제로 "확장 프로그램 추가..." 링크가 나타났습니다. 버튼과 패널을 다시 드래그해 보았는데 예외 없이 각 컨트롤의 SmartTasks 패널 하단에 "확장 프로그램 추가..." 링크가 나타났습니다.
최근에는 페이지 저장, 삭제, 닫기 등의 기능을 액션으로 추상화할 계획입니다. 각 액션은 사용자 지정 웹 컨트롤에 해당합니다. 액션 컨트롤(예: Button)을 연결하면 대상 컨트롤에 함수가 포함됩니다. 페이지 저장, 삭제, 닫기 등. WebForm 디자이너의 Button 컨트롤에 작업을 쉽게 연결하는 방법은 무엇입니까? 내가 원하는 것은 "확장 프로그램 추가..."와 같은 것입니다.
사용자 정의 서버 컨트롤을 개발한 친구들은 컨트롤에 SmartTasks를 추가하려면 ControlDesigner의 ActionLists 속성을 재정의하고 자신만의 DesignerActionList를 구현해야 한다는 것을 알아야 합니다. 분명히 TextBox는 AjaxControlToolkit의 존재를 알지 못하므로 DesignerActionMethodItem과 같은 "확장 추가..."는 추가되지 않습니다. 그렇다면 .net 프레임워크는 DesignerActionItem을 다른 컨트롤에 "동적으로 주입"할 수 있는 일종의 인터페이스를 제공합니까?
AjaxControlToolKit.dll에 대한 조사를 통해 이러한 확장 컨트롤의 디자이너는 "확장 추가" 작업 제공에 대한 책임이 없으며 해당 확장에 해당하는 확장 콘텐츠 제공에 대한 책임만 있다는 사실을 발견했습니다. Visual Studio의 웹폼 디자이너입니다. 와서 공부하세요. 리플렉터를 사용하여 Microsoft Visual Studio 9.0Common7IDEMicrosoft.Web.Design.Client.dll을 열고 IWebSmartTasksProvider 인터페이스를 찾으세요. 이 인터페이스에는 GetDesignerActionLists 메서드가 있습니다. 이 메서드의 반환 값은 SmartTasks 패널에 표시되는 콘텐츠여야 합니다. . 이 인터페이스에는 DataFormDesigner, DataFormXslValueOfDesigner 및 ElementDesigner의 세 가지 구현 클래스가 있습니다. ElementDesigner가 가장 일반적으로 사용되는 구현 클래스여야 한다는 것은 이 세 클래스의 이름을 통해 추론할 수 있습니다. ElementDesigner의 GetDesignerActionLists 메서드는 다음과 같이 구현됩니다.
1: DesignerActionListCollection IWebSmartTasksProvider.GetDesignerActionLists()
2: {
3: DesignerActionListCollection 구성 요소 작업 = null;
4: if (this.Designer != null)
5: {
6: DesignerActionService 서비스 = (DesignerActionService) base.DesignerHost.GetService(typeof(DesignerActionService));
7: if (서비스 != null)
8: {
9: componentActions = service.GetComponentActions(this.Designer.Component);
10: }
11: }
12: if (comComponentActions == null)
13: {
14: componentActions = new DesignerActionListCollection();
15: }
16: 컴포넌트 액션을 반환합니다.
17: }
18:
19:
20:
스물하나:
위 코드를 보면 최종 DesignerActionListCollection은 System.Design 어셈블리 아래 System.ComponentModel.Design.DesignerActionService 클래스의 GetComponentActions와 Microsoft.Web.Design.Client 아래 Microsoft.Web.Design.WebFormDesigner에 의해 결정되는 것을 알 수 있습니다. dll.+WebDesignerActionService는 이 클래스를 상속하며 구현은 다음과 같습니다.
1: 보호된 재정의 void GetComponentDesignerActions(IComponent 구성 요소, DesignerActionListCollection actionLists)
2: {
3: 제어 제어 = 제어로서의 구성 요소;
4: ElementDesigner 상위 = null;
5: if (제어 != null)
6: {
7: 부모 = ElementDesigner.GetElementDesigner(control);
8: }
9: if ((parent == null) || !parent.InTemplateMode)
10: {
11: base.GetComponentDesignerActions(컴포넌트, actionLists);
12: if ((parent != null) && (parent.Designer != null))
13: {
14: ControlDesigner 디자이너 = ControlDesigner로서의 parent.Designer;
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))
스물 하나: {
22: IWebDataFormElementCallback dataFormElementCallback = parent.Element.GetDataFormElementCallback();
23: if (dataFormElementCallback != null)
스물넷: {
25: DataFormElementActionList 목록 = 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(부모, actionLists);
4: }
여기에 "확장자 추가" 액션이 추가된 것 같습니다. ExtenderControlHelper.AddActionLists의 구현을 계속 살펴보세요.
1: 공개 무효 AddActionLists(ElementDesigner 요소, DesignerActionListCollection 목록)
2: {
3: list.Add(new ControlExtenderActionList(element));
4: ExtenderControl 구성 요소 = ExtenderControl로서의 element.Designer.Component;
5: 제어 제어 = element.Designer.Component를 제어로;
6: if ((컴포넌트 == null) && (제어 != null))
7: {
8: IExtenderInformationService 서비스 = (IExtenderInformationService) control.Site.GetService(typeof(IExtenderInformationService));
9: if (서비스 != null)
10: {
11: foreach(service.GetAppliedExtenders(control)의 제어 control3)
12: {
13: list.Add(new HoistedExtenderActionList(element.Designer, control3));
14: }
15: }
16: }
17: }
18:
19:
20:
스물하나:
이 메서드의 첫 번째 문장은lists.Add(new ControlExtenderActionList(element))입니다. ControlExtenderActionList는 System.ComponentModel.Design.DesignerActionList를 상속합니다. 해당 메서드는 다음과 같이 정의됩니다.
1: 공개 재정의 DesignerActionItemCollection GetSortedActionItems()
2: {
3: 컨트롤 컴포넌트 = (컨트롤) this._htmlDesigner.Component;
4: DesignerActionItemCollection 항목 = new DesignerActionItemCollection();
5: IExtenderInformationService 서비스 = (IExtenderInformationService) 구성 요소.Site.GetService(typeof(IExtenderInformationService));
6: 문자열 카테고리 = SR.GetString(SR.Ids.SmartTasksLabelExtenderSection, CultureInfo.CurrentUICulture);
7: if (service.IsControlExtendible(구성요소))
8: {
9: 문자열 displayName = SR.GetString(SR.Ids.SmartTasksAddExtender, CultureInfo.CurrentUICulture);
10: items.Add(new DesignerActionMethodItem(this, "AddExtender", displayName, 카테고리, true));
11: }
12: if (service.IsControlExtended(구성 요소))
13: {
14: 문자열 str3 = SR.GetString(SR.Ids.SmartTasksRemoveExtender, CultureInfo.CurrentUICulture);
15: items.Add(new DesignerActionMethodItem(this, "RemoveExtender", str3, 카테고리, true));
16: }
17: 반품 항목;
18: }
19:
이제 Visual Studio의 웹 양식 디자이너에 "확장 추가" 작업이 하드 코딩되어 있음이 분명해졌습니다. .net 프레임워크는 유사한 작업을 추가할 수 있는 해당 인터페이스를 제공하지 않습니다. 하지만 내가 원하는 효과는 "작업 추가" 작업을 추가하는 것이므로 이를 달성하기 위해 AjaxControlToolkit 메서드를 참조할 수는 없습니다.
돌아가서 Microsoft.Web.Design.WebFormDesigner+WebDesignerActionService 클래스의 GetComponentActions 메서드를 다시 검토하고 다음과 같이 기본 클래스 System.Web.UI.Design.WebFormsDesignerActionService(System.Design 어셈블리 아래)의 정의를 찾습니다.
1: 보호된 재정의 void GetComponentDesignerActions(IComponent 구성 요소, DesignerActionListCollection actionLists)
2: {
3: if (구성 요소 == null)
4: {
5: 새로운 ArgumentNullException("컴포넌트");를 던집니다.
6: }
7: if (actionLists == null)
8: {
9: 새로운 ArgumentNullException("actionLists")을 던집니다.
10: }
11: IServiceContainer 사이트 = IServiceContainer로서의 component.Site;
12: if (사이트 != null)
13: {
14: DesignerCommandSet 서비스 = (DesignerCommandSet) site.GetService(typeof(DesignerCommandSet));
15: if (서비스 != null)
16: {
17: DesignerActionListCollection 목록 = service.ActionLists;
18: if (목록 != null)
19: {
20: actionLists.AddRange(목록);
스물하나: }
스물둘: }
23: if ((actionLists.Count == 0) || ((actionLists.Count == 1) && (actionLists[0]은 ControlDesigner.ControlDesignerActionList입니다)))
스물넷: {
25: DesignerVerbCollection 동사 = service.Verbs;
26: if ((동사 != null) && (verbs.Count != 0))
27: {
28: DesignerVerb[] array = new DesignerVerb[verbs.Count];
29: verbs.CopyTo(배열, 0);
30: actionLists.Add(new DesignerActionVerbList(array));
31: }
32: }
33: }
34: }
35:
36:
37:
38:
위 코드를 연구하면 DesignerActionListCollection이 DesignerCommandSet 서비스의 ActionLists 속성에 의해 반환되고 이 서비스는 다른 DesignerCommandSet를 작성하고 DesignerCommandSet에서 가져오는 한 구성 요소의 사이트에서 가져오는 것을 볼 수 있습니다. 사이트는 내 것입니다. 이 서비스를 작성하세요. 마지막으로 진입점을 찾았습니다. 구체적인 방법은 다음과 같습니다.
먼저 다음과 같이 DesignerCommandSet를 상속하는 클래스를 만듭니다.
1: 공용 클래스 MyDesignerCommandSet : DesignerCommandSet
2: {
3: 전용 ComponentDesigner _comComponentDesigner;
4:
5: 공용 MyDesignerCommandSet(ComponentDesigner 구성 요소 디자이너)
6: {
7: _comComponentDesigner = 컴포넌트디자이너;
8: }
9:
10: 공개 재정의 ICollection GetCommands(문자열 이름)
11: {
12: if (name.Equals("ActionLists"))
13: {
14: GetActionLists()를 반환합니다.
15: }
16: return base.GetCommands(이름);
17: }
18:
19: 비공개 DesignerActionListCollection GetActionLists()
20: {
21: //컨트롤의 원래 DesignerActionList를 먼저 가져옵니다.
22: DesignerActionListCollection 목록 = _comComponentDesigner.ActionLists;
스물셋:
24: //"액션 추가" DesignerActionList 추가
25: list.Add(new ActionList(_comComponentDesigner));
26: 반환 목록;
27: }
28:
29: 내부 클래스 ActionList : DesignerActionList
30: {
31: 개인 DesignerActionItemCollection _actions;
32:
33: 공개 ActionList(IDesigner 디자이너)
34: : 베이스(디자이너.컴포넌트)
35: {
36: }
37: 공개 재정의 DesignerActionItemCollection GetSortedActionItems()
38: {
39: if (_actions == null)
40: {
41: const string actionCategory = "작업";
42: _actions = new DesignerActionItemCollection();
43: _actions.Add(new DesignerActionMethodItem(this, "AddAction", "액션 추가...", actionCategory, true));
44: }
45: _actions를 반환합니다.
46: }
47:
48: 공개 무효 AddAction()
49: {
50: //액션 로직 추가, 생략
51: }
52: }
53: }
다음 단계는 구성 요소의 Site ServiceProvider가 자체 서비스를 반환하도록 만드는 방법입니다. 방법은 사이트를 직접 작성하고 해당 구성요소의 사이트를 작성한 SIte 클래스의 객체로 만드는 것입니다.
제가 작성한 Site 클래스의 정의는 다음과 같습니다.
1: 공개 클래스 SiteProxy: ISite, IServiceContainer
2: {
3: 비공개 ISite_site;
4: 전용 ComponentDesigner _designer;
5:
6: 공개 SiteProxy(ISite 사이트, ComponentDesigner 디자이너)
7: {
8: _사이트 = 사이트;
9: _디자이너 = 디자이너;
10:
11: }
12:
13: #region I사이트 회원
14:
15: 공용 IComponentComponent
16: {
17: get { return _site.Component };
18: }
19:
20: 공용 System.ComponentModel.IContainer 컨테이너
스물 하나: {
22: get { return _site.Container }
스물셋: }
스물넷:
25: 공개 boolDesignMode
26: {
27: get { return _site.DesignMode };
28: }
29:
30: 공개 문자열 이름
31: {
32: get { return _site.Name }
33: { _site.Name = 값 } 설정
34: }
35:
36: #끝지역
37:
38: #region IServiceProvider 회원
39:
40: 공용 객체 GetService(유형 serviceType)
41: {
42: 개체 서비스 = _site.GetService(serviceType);
43:
44: if (serviceType == typeof(DesignerCommandSet) && !(_designer.Component is ExtenderControl))
45: {
46: if (service == null || !(서비스는 MyDesignerCommandSet입니다))
47: {
48: if (서비스 != null)
49: {
50: RemoveService(typeof(DesignerCommandSet));
51: }
52: //직접 작성한 DesignerCommandSet를 반환합니다.
53: 서비스 = new MyDesignerCommandSet(_designer);
54: AddService(typeof(DesignerCommandSet), 서비스);
55: }
56: }
57: 반품 서비스;
58: }
59:
60: #끝지역
61:
62: #region IServiceContainer 멤버
63:
64: public void AddService(Type serviceType, ServiceCreatorCallback 콜백, bool 승격)
65: {
66: (_site as IServiceContainer).AddService(serviceType, 콜백, 승격);
67: }
68:
69: public void AddService(유형 serviceType, ServiceCreatorCallback 콜백)
70: {
71: (_site as IServiceContainer).AddService(serviceType, callback);
72: }
73:
74: public void AddService(Type serviceType, object serviceInstance, bool Promotion)
75: {
76: (_site as IServiceContainer).AddService(serviceType, serviceInstance, 프로모션);
77: }
78:
79: public void AddService(Type serviceType, object serviceInstance)
80: {
81: (_site as IServiceContainer).AddService(serviceType, serviceInstance);
82: }
83:
84: public void RemoveService(유형 serviceType, bool 승격)
85: {
86: (_site as IServiceContainer).RemoveService(serviceType, 승격);
87: }
88:
89: 공개 무효 RemoveService(유형 serviceType)
90: {
91: (_site as IServiceContainer).RemoveService(serviceType);
92: }
93:
94: #끝지역
95: }
이 사이트의 GetService 메서드에서 가져올 서비스 유형을 결정합니다. DesignerCommandSet인 경우 생성한 MyDesignerCommandSet를 반환합니다.
다음 단계는 구성 요소의 사이트를 직접 작성한 SiteProxy로 변경하는 방법입니다. 한 가지 방법은 컨트롤의 ControlDesigner의 초기화 메서드에서 사용자 지정 컨트롤을 추가하고 다른 컨트롤의 사이트를 변경하는 것입니다. 다른 컨트롤의 사이트를 변경하려면 해당 컨트롤을 WebForm으로 드래그하기만 하면 됩니다. 방법은 vs 패키지를 작성하고 패키지에서 웹 양식 디자이너의 해당 이벤트를 캡처하는 것입니다. 첫 번째 접근 방식은 다음과 같습니다.
ActionManager라는 Control에서 상속된 새 컨트롤을 추가합니다. 이 컨트롤은 ControlDesigner만 생성하면 됩니다. ControlDesigner 클래스의 주요 코드는 다음과 같습니다.
1: 공용 클래스 ActionManagerDesigner: ControlDesigner
2: {
3: 개인 IDesignerHost _host;
4: 개인 IDictionary<IComponent, ISite> _comComponents;
5:
6: 공개 재정의 무효 초기화(IComponent 구성 요소)
7: {
8: 기본.초기화(구성 요소);
9:
10: _comComponents = new Dictionary<IComponent, ISite>();
11:
12: _host = GetService(typeof(IDesignerHost)) as IDesignerHost;
13: if (_host != null)
14: {
15: //사이트를 기존 컨트롤로 교체
16: 프로세스컴포넌트();
17:
18: IComponentChangeService 서비스 =
19: _host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
20: if (서비스 != null)
스물 하나: {
22: service.ComponentAdded += ComponentAdded;
23: service.ComponentRemoving += ComponentRemoving;
스물넷: }
25: }
26: }
27:
28: #regionProcessComponent
29:
30: 개인 무효 ProcessComponent()
31: {
32: ComponentCollection 구성요소 = _host.Container.Components;
33: foreach(컴포넌트의 IComponent 컴포넌트)
34: {
35: if (컴포넌트는 ActionControl입니다)
36: 계속;
37: 프로세스컴포넌트사이트(컴포넌트);
38: }
39: }
40:
41: #끝지역
42:
43: #region 사이트 교체
44:
45: /// <요약>
46: /// 구성 요소의 원래 사이트를 SiteProxy로 교체합니다.
47: /// </summary>
48: private void ProcessComponentSite(IComponent 컴포넌트)
49: {
50: ComponentDesigner 디자이너 = ComponentDesigner로서의 _host.GetDesigner(컴포넌트);
51: _컴포넌트[컴포넌트] = 컴포넌트.사이트;
52: 컴포넌트.Site = new SiteProxy(컴포넌트.사이트, 디자이너);
53: }
54:
55: /// <요약>
56: /// 구성 요소의 원래 사이트를 복원합니다.
57: /// </summary>
58: /// <param name="컴포넌트"></param>
59: private void RestoreComponentSite(IComponent 컴포넌트)
60: {
61: if (_comComponents.ContainsKey(컴포넌트))
62: {
63: ISite 사이트 = _컴포넌트[컴포넌트];
64: 구성요소.사이트 = 사이트;
65: _컴포넌트.제거(컴포넌트);
66: }
67: }
68:
69: #끝지역
70:
71: 구성 요소의 #region 추가, 제거, 변경
72:
73: private void ComponentRemoving(객체 전송자, ComponentEventArgs e)
74: {
75: if (e.Component가 ActionControl임)
76: {
77: 복귀;
78: }
79: //구성 요소를 삭제할 때 해당 사이트 속성을 복원해야 합니다. 그렇지 않으면 원래 사이트가 DesignerHost에 유지됩니다.
80: //이런 방식으로 같은 이름의 컴포넌트를 추가하면 "컴포넌트 이름이 중복되었습니다" 오류가 보고됩니다.
81: RestoreComponentSite(e.Component);
82: }
83:
84:
85: private void ComponentAdded(객체 전송자, ComponentEventArgs e)
86: {
87: if (e.Component가 ActionControl임)
88: {
89: 복귀;
90: }
91: ProcessComponentSite(e.Component);
92: }
93:
94: #끝지역
95:
96: #region 처리
97:
98: 보호된 재정의 void Dispose(bool disposing)
99: {
100: if (_host != null)
101: {
102: IComponentChangeService 서비스 =
103: _host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
104: if (서비스 != null)
105: {
106: service.ComponentAdded -= ComponentAdded;
107: service.ComponentRemoving -= 구성요소 제거;
108: }
109: }
110: 베이스.Dispose(처리);
111: }
112:
113: #끝지역
114: }
이 시점에서 ActionManager 컨트롤을 웹 양식 디자이너로 드래그하면 다른 컨트롤의 스마트 작업 패널에서 "액션 추가..." 링크를 볼 수 있습니다. 그러나 이 방법을 사용하려면 웹 양식 디자이너에 추가 컨트롤을 배치해야 합니다. 이 컨트롤은 디자인 타임에만 유용하고 런타임에는 쓸모가 없으므로 가장 좋은 방법은 대 패키지를 개발하는 두 번째 방법입니다. 패키지의 초기화 메소드에서 IDesignerEventService의 DesignerCreated 이벤트를 등록한 후 IDesignerHost 및 IComponentChangeService를 통해 제어 사이트를 변경하는 목적을 달성합니다. 구체적인 구현은 위와 유사하므로 더 이상 작성하지 않겠습니다.