A designer is a class that governs the behavior of a component on the design surface. In the .NET Framework, the System.ComponentModel.Design.IDesigner interface specifies core designer functionality. The System.ComponentModel.Design.ComponentDesigner class implements this interface and provides base designer functionality to all ( designable ) components . The core designer architecture is the same in Web Forms and Windows Forms. However, Web Forms and Windows Forms use different rendering engines. The Web Forms designer uses Microsoft Internet Explorer as its rendering engine, while the Windows Forms designer uses GDI+ for rendering. To provide base design-time rendering logic that is relevant to each rendering engine, Web Forms and Windows Forms define their own base designer classes that derive from ComponentDesigner . The base class for Web Forms control designers, System.Web.UI.Design.ControlDesigner , implements the plumbing to generate HTML for rendering a control at design time. The base class for Windows Forms control designers, System.Windows.Forms.Design.ControlDesigner , provides the plumbing for drawing in a native window by using GDI+ at design time. The System.Web.UI.Design.ControlDesigner class is associated with the base Control class (via the DesignerAttribute ) and thus is associated with a server control by default. From here on, we'll simply refer to the base designer class for server controls as ControlDesigner because there is no potential for ambiguity. (We do not describe designers for Windows Forms controls in this book.) To develop a custom control designer, you must implement a class that derives directly or indirectly from ControlDesigner and overrides the methods described in the following list. You can then associate the designer class with your control by applying the DesignerAttribute metadata attribute, which we'll describe in greater detail at the end of this section.
The most important method to implement in a designer is the GetDesignTimeHtml method. The pseudocode in Listing 15-1 shows the implementation pattern for this method. Listing 15-1 Pattern for implementing the GetDesignTimeHtml methodpublicoverridestringGetDesignTimeHtml(){ stringdesignTimeHtml=null; try{ //Makechangestothecontrolifneededtoensurea //meaningfuldesign-timerendering. //Nextinvokethebaseclass'smethod. designTimeHtml=base.GetDesignTimeHtml() } catch(Exceptionex){ designTimeHtml=GetErrorDesignTimeHtml(ex); } finally{ //Undoanychangesthatyoumadeinthetryblock. } if((designTimeHtml==null)(designTimeHtml.Length==0)){ designTimeHtml=GetEmptyDesignTimeHtml(); } returndesignTimeHtml; } Note If you make changes to a control in GetDesignTimeHtml (for example, a Label renders the value of its ID property as its text at design time when its Text property has not been set), you must undo those changes after you have generated the HTML string to render. You should enclose your code in try / catch / finally blocks so that you can undo changes (if any) made in the try block in the finally block. The base ControlDesigner class sets a control's Visible property to true in the try block of its GetDesignTimeHtml method so that every control on the page is rendered at design time, even if the control's Visible property is set to false . ControlDesigner sets the value of the Visible property back to its actual value in the finally block of the GetDesignTimeHtml method. ASP.NET provides two additional base designer classes that derive from ControlDesigner and provide designer functionality for common scenarios:
We'll now look at examples that illustrate how you can extend the base designer classes to implement designers for common categories of server controls. Composite Control Designers ”The CompositeControlDesigner ExampleAs we described in Chapter 12, "Composite Controls," ASP.NET does not provide a base designer class for composite controls. When you implement a composite control, such as the CompositeLogin control we described in Chapter 12, you must implement a designer that ensures that child controls are rendered on the design surface. Listing 15-2 shows the code for a designer that you can use as the base class for composite controls. Listing 15-2 CompositeControlDesigner.csusingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Web.UI; usingSystem.Web.UI.Design; namespaceMSPress.ServerControls.Design{ publicclassCompositeControlDesigner:ControlDesigner{ publicoverridestringGetDesignTimeHtml(){ //Retrievethecontrolstoensuretheyarecreated. ControlCollectioncontrols=((Control)Component).Controls; returnbase.GetDesignTimeHtml(); } publicoverridevoidInitialize(IComponentcomponent){ if(!(componentisControl)&& !(componentisINamingContainer)){ thrownewArgumentException("Componentmustbeacontainercontrol.", "component"); } base.Initialize(component); } } } Although this designer ensures that child controls are rendered, it is not a read-write control designer ”that is, a page developer cannot select a child control on the design surface to access its object model in the property browser. Later in this section, we'll describe the ReadWriteControlDesigner class, which allows page developers to select a control's contents on the design surface. However, a ReadWriteControlDesigner renders only those child controls that exist within the control's tags in the declarative syntax of the .aspx page. In short, the design-time architecture currently does not provide any functionality to allow a page developer to access child controls that are programmatically added to your control. To associate the CompositeControlDesigner designer with a composite control, apply the DesignerAttribute metadata attribute, as shown in the following example: [ Designer(typeof(CompositeControlDesigner)) ] publicclassCompositeLogin:WebControl{...} We'll describe other variations of the DesignerAttribute at the end of this section. Templated Control Designers ”The ContactInfoDesigner ExampleWe'll now examine how to implement a designer that provides a UI for editing template properties in a WYSIWYG fashion. To illustrate the concepts, we'll create a ContactInfoDesigner designer for the ContactInfo templated control we implemented in Chapter 12. Figure 15-2 shows the ContactInfo control with which the ContactInfo Designer class is associated. The Contact Template command appears when the page developer right-clicks the control to access the context menu and selects the Edit Template command. Figure 15-2. The ContactInfo control with which ContactInfoDesigner is associated in the Web Forms designer in Visual Studio .NET
Figure 15-3 shows the control after the page developer chooses the Contact Template command shown in Figure 15-2. The control now displays the template editing UI, and the page developer can create the template in a WYSIWYG fashion by dragging controls from the toolbox onto the template editing surface. To set properties of controls in the template, the page developer selects controls on the template surface and edits their properties in the property browser. To complete editing the template, the page developer right-clicks the control to access the context menu and then chooses the End Template Editing command. Figure 15-3. The template editing UI created by the ContactInfoDesigner class that is associated with the ContactInfo control
Before we examine the code for the ContactInfoDesigner class, here is a high-level overview of the steps required to implement a designer for a templated control. You must define a class that derives from System.Web.UI.Design.TemplatedControlDesigner and implement the following abstract methods of the TemplatedControlDesigner base class:
A templated control designer also uses an ITemplateEditingService service, which is one of the services represented by the Design-Time Services box shown in Figure 15-1. Listing 15-3 shows the code for the ContactInfoDesigner class. Listing 15-3 ContactInfoDesigner.csusingSystem; usingSystem.Collections; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Diagnostics; usingSystem.Web.UI; usingSystem.Web.UI.Design; usingSystem.Web.UI.WebControls; usingMSPress.ServerControls; namespaceMSPress.ServerControls.Design{ publicclassContactInfoDesigner:TemplatedControlDesigner{ privateTemplateEditingVerb[]_templateEditingVerbs; #regionDesign-timeHTML publicoverridestringGetDesignTimeHtml(){ ContactInfocontrol=(ContactInfo)Component; if(control.ContactTemplate==null){ returnGetEmptyDesignTimeHtml(); } stringdesignTimeHtml=String.Empty; try{ control.DataBind(); designTimeHtml=base.GetDesignTimeHtml(); } catch(Exceptione){ designTimeHtml=GetErrorDesignTimeHtml(e); } returndesignTimeHtml; } protectedoverridestringGetEmptyDesignTimeHtml(){ returnCreatePlaceHolderDesignTimeHtml("Right-clickto " + "edittheContactTemplateproperty. " + "<br>IftheContactTemplateisnotspecified,a " + "defaulttemplateisusedatruntime."); } protectedoverridestringGetErrorDesignTimeHtml(Exceptione){ returnCreatePlaceHolderDesignTimeHtml("Therewasan " + "errorrenderingtheContactInfocontrol."); } #endregionDesign-timeHTML #regionTemplate-editingFunctionality protectedoverride TemplateEditingVerb[]GetCachedTemplateEditingVerbs(){ if(_templateEditingVerbs==null){ _templateEditingVerbs=newTemplateEditingVerb[1]; _templateEditingVerbs[0]= newTemplateEditingVerb("ContactTemplate", 0,this); } return_templateEditingVerbs; } protectedoverride ITemplateEditingFrameCreateTemplateEditingFrame(TemplateEditingVerbverb){ ITemplateEditingFrameframe=null; if((_templateEditingVerbs!=null)&& (_templateEditingVerbs[0]==verb)){ ITemplateEditingServiceteService= (ITemplateEditingService)GetService(typeof(ITemplateEditingService)); if(teService!=null){ Stylestyle= ((ContactInfo)Component).ControlStyle; frame=teService.CreateFrame(this,verb.Text, newstring[]{ "ContactTemplate" },style, null); } } returnframe; } privatevoidDisposeTemplateEditingVerbs(){ if(_templateEditingVerbs!=null){ _templateEditingVerbs[0].Dispose(); _templateEditingVerbs=null; } } publicoverridestringGetTemplateContent(ITemplateEditingFrameeditingFrame,stringtemplateName, outboolallowEditing){ stringcontent=String.Empty; allowEditing=true; if((_templateEditingVerbs!=null)&& (_templateEditingVerbs[0]==editingFrame.Verb)){ ITemplatecurrentTemplate= ((ContactInfo)Component).ContactTemplate; if(currentTemplate!=null){ content=GetTextFromTemplate(currentTemplate); } } returncontent; } publicoverridevoidSetTemplateContent(ITemplateEditingFrameeditingFrame,stringtemplateName, stringtemplateContent){ if((_templateEditingVerbs!=null)&& (_templateEditingVerbs[0]==editingFrame.Verb)){ ContactInfocontrol=(ContactInfo)Component; ITemplatenewTemplate=null; if((templateContent!=null)&& (templateContent.Length!=0)){ newTemplate=GetTemplateFromText(templateContent); } control.ContactTemplate=newTemplate; } } #endregionTemplate-editingFunctionality publicoverrideboolAllowResize{ get{ booltemplateExists= ((ContactInfo)Component).ContactTemplate!=null; //Whentemplatesarenotdefined,renderaread-only //fixed-sizeblock.Oncetemplatesaredefinedor //arebeingedited,thecontrolshouldallowresizing. returntemplateExistsInTemplateMode; } } protectedoverridevoidDispose(booldisposing){ if(disposing){ DisposeTemplateEditingVerbs(); } base.Dispose(disposing); } publicoverridevoidInitialize(IComponentcomponent){ if(!(componentisContactInfo)){ thrownewArgumentException("ComponentmustbeaContactInfocontrol.", "component"); } base.Initialize(component); } publicoverridevoidOnComponentChanged(objectsender, ComponentChangedEventArgsce){ base.OnComponentChanged(sender,ce); if(ce.Member!=null){ stringname=ce.Member.Name; if(name.Equals("Font") name.Equals("ForeColor") name.Equals("BackColor")){ DisposeTemplateEditingVerbs(); } } } } } ContactInfoDesigner demonstrates how to implement the abstract methods of the base TemplatedControlDesigner class by performing the following tasks :
In addition, ContactInfoDesigner overrides the following members it inherits from ControlDesigner , which is the base class for TemplatedControlDesigner :
|