DatePicker and DatePickerDesigner Implementation


DatePicker and DatePickerDesigner Implementation

The DatePicker implementation is based on several of the control authoring concepts that we described in Part III of the book, "Server Controls ”Nuts and Bolts." This section illustrates those concepts by using fragments from the source code for the DatePicker control. You'll find the complete implementation in the book's DatePicker.cs and DatePickerDesigner.cs sample files inside the WebControls project.

Note

You can compile the control without using the Microsoft Visual Studio .NET project. Navigate to the directory that contains the sample file (for example, c:\BookCode\CSharp), and run the BuildWebControls.bat batch file, which simply makes a call to the C# compiler with the right set of command-line arguments.


Composite Control

As we mentioned earlier, DatePicker is a composite control that contains three child controls within its Controls collection: a TextBox , an Image , and a RegularExpressionValidator . Composite controls were described in detail in Chapter 12, "Composite Controls."

Listing 21-2 illustrates general concepts that apply to all composite controls.

Listing 21-2 The essence of implementing a composite control
 publicclassDatePicker:WebControl,  INamingContainer  { privateTextBox_dateTextBox; privateImage_pickerImage; privateImage_errorImage; privateRegularExpressionValidator_dateValidator;   publicoverrideControlCollectionControls  { get{ //AlwaysoverridetheControlspropertyand //ensurethatitsControlscollectionisvalid. EnsureChildControls(); returnbase.Controls; } } 
  protectedoverridevoidCreateChildControls()  { _dateTextBox=newTextBox(); _pickerImage=newImage(); _errorImage=newImage(); _dateValidator=newRegularExpressionValidator(); //EnableViewStateistruefortheTextBoxandfalseforall //otherchildcontrols. //TheTextBoxneedsviewstateinitsTextChangedevent //implementation.However,alltheothercontrolsaresetup //hereorcustomizedduringRender.Therefore,theydonot //contributetoviewstate.Additionally,settingtheir //EnableViewStatetofalsetellsthepageframeworkto //skipthemcompletely,whichmakesforasmallperformance //improvement. _dateTextBox.ID="dateTextBox"; _dateTextBox.MaxLength=10; _dateTextBox.TextChanged+= newEventHandler(this.dateTextBox_TextChanged); _pickerImage.EnableViewState=false; _pickerImage.ImageUrl=GetClientFileurl(/books/1/539/1/html/2/Picker.gif); _pickerImage.ImageAlign=ImageAlign.AbsMiddle; _pickerImage.Attributes["hspace"]="4"; _pickerImage.Width=newUnit(34); _pickerImage.Height=newUnit(21); _errorImage.EnableViewState=false; _errorImage.ImageUrl=GetClientFileurl(/books/1/539/1/html/2/Error.gif); _errorImage.ImageAlign=ImageAlign.AbsMiddle; _errorImage.Width=newUnit(16); _errorImage.Height=newUnit(16); _dateValidator.EnableViewState=false; _dateValidator.ControlToValidate="dateTextBox"; _dateValidator.ValidationExpression= "^\s*(\d{1,2})([-./])(\d{1,2})\2"+ "((\d{4})(\d{2}))\s*$"; _dateValidator.Display=ValidatorDisplay.Dynamic; _dateValidator.Controls.Add(_errorImage); //Allthecontrolsarefullyinitializedwiththeir //propertyvaluesbeforetheyareaddedtothecontroltree. //Thisway,thepropertyvaluesbecomepartofthecontrols' //initialstateanddonotcontributetoviewstate. 
 Controls.Add(_dateTextBox); Controls.Add(_pickerImage); Controls.Add(_dateValidator); }  } 

Like all composite controls, the DatePicker implements the INaming ­Container interface. This interface is a marker interface, which causes the page framework to create a new scope for names or IDs of controls contained inside the control. This allows the DatePicker to assign its contained TextBox control an ID of "dateTextBox" without worrying about multiple controls with the same ID (should the page developer place more than a single DatePicker control on the same page). A new naming scope under the control also allows the page framework to uniquely identify the TextBox so that it can correctly load postback data containing the user 's input to update its text.

DatePicker implements all the logic for creating its contained controls by overriding the CreateChildControls method. The DatePicker control overrides this method to create and initialize its contained TextBox , Image , and RegularExpressionValidator controls. DatePicker then adds these other controls into its Controls collection in the order in which they should be rendered. Note that the DatePicker optimizes its view state usage by initializing the controls before adding them into its Controls collection and by setting the EnableViewState property of some of its controls to false .

DatePicker , like all composite controls, overrides its inherited Controls property to ensure that its Controls collection is valid by calling the EnsureChildControls method. This guarantees that any other code accessing the DatePicker control's contained controls is handed the right set of controls. If the control collection needs to be updated, DatePicker should set its ChildControls ­Created property to false . This will cause the control's CreateChildControls implementation to be called the next time a call is made to EnsureChildControls .

Delegated Properties

Delegated properties are properties exposed by a composite control that rely on properties implemented by contained (child) controls. Delegated properties allow the users of a composite control to indirectly access properties of its child controls without directly referencing the children. The DatePicker implementation gives general guidelines for implementing properties of this nature.

Listing 21-3 illustrates properties that the DatePicker control delegates to its contained TextBox and RegularExpressionValidator controls.

Listing 21-3 Defining and implementing delegated properties
 [  DefaultValue(false)  ] publicvirtualboolReadOnly{ get{  EnsureChildControls();  return_dateTextBox.ReadOnly; } set{  EnsureChildControls();  _dateTextBox.ReadOnly=value; } } publicvirtualstringValidationMessage{ get{ strings=(string)ViewState["ValidationMessage"]; return(s==null)?String.Empty:s; } set{ ViewState["ValidationMessage"]=value; } } protectedoverridevoidRenderContents(HtmlTextWriterwriter){  //Firstpreparethetextboxforrenderingbyapplyingthis //control'sstyleandanyattributestoit. if(ControlStyleCreated){  _dateTextBox.ApplyStyle(ControlStyle);  }  _dateTextBox.CopyBaseAttributes(this);  _dateTextBox.RenderControl(writer);  if(_dateValidator.Visible){ //DelegatetheValidationMessagepropertyofthecontrol //tothevalidator,beforeitgetsrendered.  _dateValidator.ErrorMessage=ValidationMessage;  _dateValidator.RenderControl(writer); } } 

DatePicker illustrates two kinds of property delegation: immediate dele ­gation and delayed delegation . We'll discuss both these delegation types now.

Immediate Delegation

The DatePicker implements its ReadOnly property by delegating immediately to the ReadOnly property of the contained TextBox control in its property accessors. Because this property is immediately delegated, both the getter and setter first call EnsureChildControls to ensure that the current Controls collection is valid. Furthermore, the DatePicker chooses the same default value for its property as the TextBox so that the semantics of its property are the same as those of the property implementation to which it delegates. In this case, the TextBox is responsible for managing the value of the ReadOnly property in its own view state.

Delayed Delegation

The ValidationMessage property is an example of a property that is not immediately delegated to the contained RegularExpressionValidator . Instead, the DatePicker delegates this property value to its contained validator control right before it is rendered. This delayed delegation is necessary because the EnableViewState property of the contained validator is set to false to minimize the resulting view state, as shown in Listing 21-2. As a result, the DatePicker control (the container) is responsible for managing the ValidationMessage property value in its own view state. Because view state is collected prior to rendering, any changes made during the Render phase do not increase the view state size .

Another example of delegation that is not immediately obvious is the delegation of the DatePicker control's style properties and attributes collection. These are applied to the contained TextBox control just before the control is rendered. At its core , a DatePicker is a variation of a TextBox and therefore delegates its entire visual appearance to its contained TextBox while managing those properties in its own view state.

Styles and State Management

The DatePicker contains various style properties that enable the page developer to customize its rendered UI ”for example, CalendarStyle , DayStyle , and TodayDayStyle . We discussed styles in Chapter 11, "Styles in Controls," and we discussed state management concepts in Chapter 7, "Simple Properties and View State" and Chapter 10, "Complex Properties and State Management." As with delegated properties, styles allow a page developer to customize the contents of a composite control without having to understand the structure of the composite control's control tree and index into its Controls collection.

Styles have the ability to manage their properties in view state. However, it is the responsibility of the control using a style to include the style instance in its own implementation of view state management, as shown in Listing 21-4.

Listing 21-4 Implementing style properties and managing their view state
 privateTableStyle_calendarStyle; publicvirtualTableStyle  CalendarStyle  { get{ if(_calendarStyle==null){ _calendarStyle=newTableStyle(); if(IsTrackingViewState){ ((IStateManager)_calendarStyle).TrackViewState(); } } return_calendarStyle; } }  protectedoverridevoidLoadViewState(objectsavedState)  { objectbaseState=null; object[]myState=null; if(savedState!=null){ myState=(object[])savedState; if(myState.Length!=8){ thrownewArgumentException("Invalidviewstate"); } baseState=myState[0]; } //Alwayscallthebaseclass,evenifthestateisnull,so //thatthebaseclassgetsachancetofullyimplementits //LoadViewStatefunctionality. base.LoadViewState(baseState); if(myState==null){ return; } //Forperformancereasons,thestylesarecreatedonlyifstate //existsforthem.Itisespeciallyimportanttooptimize //theircreationincomplexcontrolsbecausestylesare //relativelycomplexobjectsandhavean //impactontherenderinglogic. if(myState[1]!=null) ((IStateManager)CalendarStyle).LoadViewState(myState[1]);  }  protectedoverrideobjectSaveViewState()  { object[]myState=newobject[8]; 
 //Again,notethatthestylesaresavedonlyiftheyhave //beencreated. myState[0]=base.SaveViewState(); myState[1]=(_calendarStyle!=null)? ((IStateManager)_calendarStyle).SaveViewState():null;  for(inti=0;i<8;i++){ if(myState[i]!=null){ returnmyState; } } //Anotherperformanceoptimization.Ifnomodificationsweremade //toanypropertiesfromtheirpersistedstate,theviewstate //forthiscontrolisnull.Returningnullratherthananarray //ofnullvalueshelpsminimizetheviewstatesignificantly. returnnull; }  protectedoverridevoidTrackViewState()  { base.TrackViewState(); //Again,notethatthetrackingispropagatedonlytothosestyles //thathavebeencreated.Newstylescreatedthereafterwillbe //markedastrackingviewstatewhentheyarecreatedondemand. if(_calendarStyle!=null) ((IStateManager)_calendarStyle).TrackViewState();  } 

DatePicker implements a read-only property for each of its style properties, such as CalendarStyle . In the property getter, DatePicker creates an instance of a TableStyle on demand. It is important to create styles only when required because they can impact performance by affecting the size of view state and can impact the complexity of the rendering process. Style properties are always implemented as read-only properties. This allows the control to assume complete responsibility for the style's state management. The control could not make this assumption if the property was read-write and the control could be handed a reference to an external style object, causing the style to be managed in view state twice ”once by the real owner of the style and another time by the control that was handed a reference.

DatePicker overrides the SaveViewState method to return an object array that contains the view state of its base class and the view state for each of its style properties. The view state for the base class stored in the first array entry contains various properties, including ForeColor , Font , Height , and Width . The other array entries contain view state for any style objects that have been instantiated . This array is returned as the view state for the DatePicker if any one of its array entries contains a non-null value. If the array does not contain any view state, the control returns null for its own view state. This is done to optimize the size of the view state.

DatePicker implements the corresponding logic of restoring view state to its style properties by overriding the LoadViewState method. The state object handed to the control is cast to an object array. The LoadViewState override should always call its base class's implementation of LoadViewState . DatePicker hands the value in the first entry of the array to its base class. For each of the other array entries, the implementation loads any non-null view state into the corresponding style properties. Because the implementation uses the style properties, any necessary style objects are created on demand.

Finally, DatePicker allows its style properties to manage their values in view state by overriding the TrackViewState method. The page framework calls the TrackViewState method to mark the end of the Initialize phase, after which property value changes are tracked in view state. DatePicker overrides this method to mark the end of the Initialize phase of any style objects that have already been created. Because styles are created on demand, the style property getters also call the style's TrackViewState method if the style is being created after the control's TrackViewState method has been called, as indicated by the control's IsTrackingViewState property.

Client Script Functionality

The DatePicker uses DHTML and JavaScript to implement a pop-up calendar UI and provide month navigation on the client. The basics of using script-based functionality were described in Chapter 13, "Client-Side Behavior."

Listing 21-5 illustrates various parts of the DatePicker implementation that are responsible for providing a rich client experience.

Listing 21-5 Adding client script functionality to use the DHTML capabilities of a Web browser
 privatebool_renderClientScript; privatebool_renderPopupScript; [ DefaultValue(true) ] publicbool  EnableClientScript  { get{ objectb=ViewState["EnableClientScript"]; 
 return(b==null)?true:(bool)b; } set{ ViewState["EnableClientScript"]=value; } }  protectedoverridevoidOnPreRender(EventArgse)  { base.OnPreRender(e);  DetermineClientScriptLevel();  if(_renderClientScript){ Page.RegisterClientScriptBlock(typeof(DatePicker).FullName, GetClientScriptInclude("DatePicker.js")); } //Propagatethesettingintothevalidatoraswell,becauseit //islogicallypartofthiscontrolnow. _dateValidator.EnableClientScript=EnableClientScript; }  privatevoidDetermineClientScriptLevel()  { //Inaclientscriptenabledcontrol,alwaysdeterminewhetherto //rendertheclientscriptfunctionality. //Thedecisionshouldbebasedonbothbrowsercapabilitiesand //thepagedeveloper'schoice. _renderClientScript=false; _renderPopupScript=false; if((Page!=null)&&(Page.Request!=null)){ //Thepagedevelopercandecidetoturnoffscriptcompletely. if(EnableClientScript){ //Thenextsetofchecksinvolveslookingatthe //capabilitiesofthebrowsermakingtherequest. //TheDatePickerneedstoverifywhetherthebrowserhas //EcmaScript(JavaScript)version1.2+andwhetherthe //browsersupportsDHTMLand,optionally,DHTMLbehaviors.  HttpBrowserCapabilitiesbrowserCaps=Page.Request.Browser;  boolhasEcmaScript= (browserCaps.EcmaScriptVersion.CompareTo(newVersion(1,2))>=0); boolhasDOM=(browserCaps.MSDomVersion.Major>=4); boolhasBehaviors= (browserCaps.MajorVersion>5) ((browserCaps.MajorVersion==5)&& (browserCaps.MinorVersion>=.5)); 
 _renderClientScript=hasEcmaScript&&hasDOM; _renderPopupScript=_renderClientScript&&hasBehaviors; } } }  privatestringGetClientFileUrl(stringfileName)  { if(ClientFilesUrlPrefix==null){ //Usetheconfigsettingtodeterminewheretheclientfiles //arelocated.Clientfilesarelocatedintheaspnet_client //v-rootandthendistributedintosubfoldersbyassemblyname //andassemblyversion. stringlocation=null; if(Context!=null){ IDictionaryconfigData= (IDictionary)Context.GetConfig("system.web/webControls"); if(configData!=null){ location=(string)configData["clientScriptsLocation"]; } } if(location==null){ location=String.Empty; } elseif(location.IndexOf("{0}")>=0){ AssemblyNameassemblyName=GetType().Assembly.GetName(); stringassembly= assemblyName.Name.Replace('.','_').ToLower(); stringversion= assemblyName.Version.ToString().Replace('.','_'); location=String.Format(location,assembly,version); } ClientFilesUrlPrefix=location; } returnClientFilesUrlPrefix+fileName; }  privatestringGetClientScriptInclude(stringscriptFile)  { return"<scriptlanguage=\"JavaScript\"src=\""+ GetClientFileUrl(scriptFile)+"\"></script>"; } privatestringGetCssFromStyle(Stylestyle){  } 
  protectedoverridevoidAddAttributesToRender(HtmlTextWriterwriter)  {  if(_renderClientScript){ if(_renderPopupScript){ writer.AddAttribute("dp_htcURL", GetClientFileUrl("Calendar.htc"),false); } if(AutoPostBack){ writer.AddAttribute("dp_autoPostBack","true",false); } if(_calendarStyle!=null){  strings=GetCssFromStyle(_calendarStyle); if(s.Length!=0){ writer.AddAttribute("dp_calendarStyle",s,false); } }  } }  protectedoverridevoidRenderContents(HtmlTextWriterwriter)  {  boolshowPicker=_renderClientScript;  if(showPicker){ stringpickerAction; if(_renderPopupScript){ pickerAction= "dp_showDatePickerPopup(this,document.all['"+ _dateTextBox.ClientID+"'],document.all['"+ ClientID+"'])"; } else{ pickerAction= "dp_showDatePickerFrame(this,document.all['"+ _dateTextBox.ClientID+"'],document.all['"+ ClientID+"'],document.all['"+ClientID+ "_frame'],document)"; } _pickerImage.Attributes["onclick"]=pickerAction; _pickerImage.RenderControl(writer); if(_renderPopupScript==false){ //UseanIFRAMEinsteadofaDHTMLpop-up. writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID+"_frame"); 
 writer.AddAttribute(HtmlTextWriterAttribute.Src, GetClientFileUrl("CalendarFrame.htm"));  writer.RenderBeginTag(HtmlTextWriterTag.Iframe); writer.RenderEndTag(); } }  } 

In Internet Explorer 4, DatePicker uses a floating <iframe> to implement the drop-down calendar and month navigation. In Internet Explorer 5.0 and later, the control uses new functionality offered by the browser ”DHTML behaviors and DHTML pop-up windows ”to implement the same features. On other Web browsers (often referred to as downlevel Web browsers), the control simply provides a text box. DatePicker packages its client functionality in a separate script file.

DatePicker overrides the OnPreRender method to determine the level of its client script functionality and prepare itself for rendering the HTML needed to implement its client functionality without any additional support from the page developer. When the control's client functionality is enabled, it includes a reference to its associated script file within the page by calling the RegisterClient ­ScriptBlock method of the Page class. The client script is generated only once by the page, even with multiple DatePicker controls on the page. Finally, the DatePicker delegates its choice for enabling script-based functionality to its contained RegularExpressionValidator so that the validator control can perform similar actions in its own OnPreRender implementation.

The DatePicker examines the capabilities of the requesting Web browser described by the properties of an HttpBrowserCapabilities object retrieved from Page.Request.Browser . The DatePicker checks for the availability of JavaScript 1.2 and DHTML. In addition, DatePicker checks for the availability of DHTML behaviors and DHTML pop-up windows by verifying the version of the Web browser.

DatePicker implements an EnableClientScript property whose default value is true . This allows the page developer to completely turn off the client-side functionality for a specific control instance by disabling the logic it contains to automatically detect the capabilities of the Web browser client.

The DatePicker has a number of associated client files ”the script include (DatePicker.js), the DHTML behavior (Calendar.htc), the <iframe> source (Calendar.htm), and the picker image (Picker.gif). The GetClientFileUrl method contains the logic to create URLs of the form /aspnet_client/mspress_webcontrols/1_0_0_0/DatePicker.js , which allow access to the client files installed into a shared location (c:\inetpub\ wwwroot \aspnet_client) used by ASP.NET server controls.

The final piece of implementation that enables client script functionality is the rendering logic itself. The DatePicker renders various expando (or custom) attributes ”such as dp_htcURL , dp_autoPostBack , and dp_calendarStyle ” that are used by the included client script by overriding the AddAttributesToRender method. In its Render method override, DatePicker renders the image used to implement the picker when its client script functionality is enabled. In addition, the control hooks up DHTML events such as onclick to JavaScript functions implemented in the script include, such as dp_showDatePickerPopup and dp_showDatePickerFrame . Finally, the DatePicker uses its ClientID property value in calls to the JavaScript functions so that the script functionality can uniquely identify the HTML elements associated with all DatePicker controls on the page.

Rendering

We described the rendering process and related methods in Chapter 8, "Rendering." Listing 21-6 illustrates how the DatePicker overrides various rendering methods to customize its HTML representation.

Listing 21-6 Overriding the appropriate rendering methods to customize rendering
  protectedoverridevoidAddAttributesToRender(HtmlTextWriterwriter)  { //WedonotcallthebaseclassAddAttributesToRender //because,intheDatePicker,theattributesandstylesapplied //tothecontrolaffectthecontainedTextBox.TheDatePicker //itselfrendersoutasa<span>,whichcontainsonlytheID //attributeandanyattributesneededforthepop-upcalendarand //clientscript.  writer.AddAttribute(HtmlTextWriterAttribute.Id,ClientID);  if(_renderClientScript){ if(_renderPopupScript){ writer.AddAttribute("dp_htcURL", GetClientFileUrl("Calendar.htc"),false); }  } }  protectedoverridevoidRender(HtmlTextWriterwriter)  { //Ensurethatthiscontrolisinsidea<formrunat="server">. 
 if(Page!=null){ Page.VerifyRenderingInServerForm(this); } base.Render(writer); }  protectedoverridevoidRenderContents(HtmlTextWriterwriter)  { //NowrendertheactualtextboxandpickerUI. Debug.Assert(_dateTextBox!=null); Debug.Assert(_pickerImage!=null); Debug.Assert(_dateValidator!=null); //First,preparethetextboxforrenderingbyapplyingthis //control'sstyleandanyattributesontoit. //ThisisdoneaspartofRender,soanychangesmadetothetext //boxarenotpersistedinviewstate.Thevaluesarealready //beingheldaspartofthiscontrol'sviewstate,sohavingthe //textboxalsostorethemwouldberedundant. if(ControlStyleCreated){ _dateTextBox.ApplyStyle(ControlStyle); } _dateTextBox.CopyBaseAttributes(this); _dateTextBox.RenderControl(writer); boolshowPicker=_renderClientScript;  if(showPicker){  _pickerImage.RenderControl(writer); }  if(_dateValidator.Visible){ _dateValidator.ErrorMessage=ValidationMessage; _dateValidator.RenderControl(writer); } } 

Usually each server control renders one top-level HTML tag. For example, the DatePicker renders a top-level <span> tag, which then contains all rendering associated with the contained controls. The DatePicker overrides the AddAttributesToRender method to specify the attributes that are rendered on its top-level tag. At a minimum, each control should render its ClientID property value as the id attribute of the top-level tag. In addition, the DatePicker renders a number of expando attributes (such as dp_htcURL ) that are used by its associated client script.

The Render method that DatePicker inherits from WebControl is responsible for rendering the begin tag, the contents, and the end tag. DatePicker simply reuses that logic. However, DatePicker overrides the Render method to verify whether the control has been correctly placed within a <form runat="server"> tag by the page developer. The DatePicker depends on form postback and enforces this requirement by making a call to the VerifyRenderingInServerForm method of the Page class.

Finally, the DatePicker overrides the default implementation of RenderContents , which simply renders all the contained children. DatePicker overrides this logic to delegate various properties to the appropriate child controls before they are rendered, as well as to optionally render the HTML corresponding to the control's client script functionality.

Events

The DatePicker control raises the DateChanged event so that the page developer can write server-side code to handle the user's date selection. Raising server-side events that correspond to user actions in the Web browser was discussed in greater detail in Chapter 9, "Control Life Cycle, Events, and Postback." The DatePicker control's event implementation is shown in Listing 21-7.

Listing 21-7 Handling and raising events
 privatestaticreadonlyobjectEventDateChanged=newobject();  publiceventEventHandlerDateChanged  { add{ Events.AddHandler(EventDateChanged,value); } remove{ Events.RemoveHandler(EventDateChanged,value); } } protectedoverridevoidCreateChildControls(){ _dateTextBox=newTextBox();   _dateTextBox.TextChanged+= newEventHandler(this.dateTextBox_TextChanged);   }  protectedvirtualvoidOnDateChanged(EventArgse)  { EventHandlerhandler=(EventHandler)Events[EventDateChanged]; 
 if(handler!=null){ handler(this,e); } } privatevoiddateTextBox_TextChanged(objectsender,EventArgse){ //HandlestheTextChangedeventofthecontainedTextBox. try{ stringtext=_dateTextBox.Text.Trim(); DateTimedate= DateTime.Parse(text,CultureInfo.InvariantCulture); //Determineswhethertheselecteddatehaschanged. if((IsDateSelected==false)!(date.Equals(SelectedDate))){ SelectedDate=date;  OnDateChanged(EventArgs.Empty);  } } catch{ //Abaddatewasentered-removeitfromtheViewState,which //effectivelysetsSelectedDatebacktothecurrentdate //andIsDateSelectedtofalse. ViewState.Remove("SelectedDate"); } } 

DatePicker defines the DateChanged event that signals that the date entered in the text box has changed. However, DatePicker does not implement its event from scratch by implementing IPostBackDataHandler or by performing any postback processing. Rather, this control bases its event implementation on the TextChanged event of its contained TextBox . In its CreateChildControls implementation, the DatePicker control wires up its dateTextBox_TextChanged method as the event handler for the TextChanged event of its contained TextBox . Thus, the DatePicker implementation reuses the existing server-side event functionality provided by an existing control. In its event handler implementation, the DatePicker creates an instance of a DateTime object by parsing the TextBox control's text, detects whether the selected date has changed, and then proceeds to raise its own DateChanged event by calling the OnDateChanged method.

Validation

The DatePicker control performs validation by containing a RegularExpressionValidator and, in addition, supports the ASP.NET validation framework by providing a validation property. Validation was discussed in Chapter 14, "Validator Controls." Listing 21-8 demonstrates using and supporting validation.

Listing 21-8 Using and supporting validation
 [  ValidationProperty("SelectedDateText")  ] publicclassDatePicker:WebControl,INamingContainer{ [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never) ] publicstringSelectedDateText{ get{ if(IsDateSelected){ returnString.Format(CultureInfo.InvariantCulture, "{0:d}",newobject[]{SelectedDate}); } returnString.Empty; } } protectedoverridevoidCreateChildControls(){ _dateTextBox=newTextBox();  _dateValidator=newRegularExpressionValidator();  _dateTextBox.ID="dateTextBox";   _dateValidator.ControlToValidate="dateTextBox";  _dateValidator.ValidationExpression= "^\s*(\d{1,2})([-./]"+ ")(\d{1,2})\2((\d{4})(\d{2}))\s*$"; _dateValidator.Display=ValidatorDisplay.Dynamic; } } 

As mentioned, the DatePicker control contains an instance of the RegularExpressionValidator control. DatePicker associates this control with the contained TextBox instance to enforce that date entries made by the user appear in either the dd/mm/yyyy or the dd/mm/yy format.

The DatePicker participates in the ASP.NET server-side validation framework by providing the SelectedDateText property for validation by using the ValidationProperty attribute on the class. The control also participates in the client-side validation framework. A validator control that is hooked up to the DatePicker control is essentially hooked up to the <span> tag rendered by the control. The client-side validation framework automatically detects and hooks up all the <input> elements within a <span> tag, and thus the contents of the <input type="text"> element rendered by the contained TextBox control are automatically validated .

Design-Time Attributes

The DatePicker implementation uses various design-time attributes to offer metadata for its class, properties, and events to enhance the design-time experience of the control on a visual design surface. We present these designer attributes in Appendix A, "Metadata Attributes."

Listing 21-9 shows a sampling of design-time attributes used in the DatePicker implementation and in ASP.NET server controls.

Listing 21-9 Designer attributes on the class, properties, and events
 [  DefaultEvent("DateChanged"), DefaultProperty("SelectedDate"), Designer(typeof(MSPress.WebControls.Design.DatePickerDesigner), typeof(IDesigner))  ] publicclassDatePicker:WebControl,INamingContainer{ [  Category("Style"), Description("Thestyleusedtocustomizethepopupcalendar."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty)  ] publicvirtualTableStyleCalendarStyle{  } 
 [  Category("Behavior"), DefaultValue(true), Description("Whethertoenabletheclientscriptbasedfunctionality."),  ] publicboolEnableClientScript{  } [  Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)  ] publicvirtualboolIsDateSelected{  } [  Category("Change"), Description("Raisedwhentheuserselectsanewdate.")  ] publiceventEventHandlerDateChanged{  } } 

The following list describes the effects of the metadata attributes used by DatePicker :

  • The DefaultEvent attribute on the class allows the designer to create and wire up an event handler when the control is double-clicked on the design surface.

  • The DefaultProperty attribute specifies the property that should be highlighted in the designer's property browser when the control is selected.

  • The Designer attribute associates a designer class specific to the control, as described in the following section.

  • The Category attribute on properties and events allows the property browser to categorize properties and events into logical groupings.

  • The Description attribute on properties and events allows the property browser to provide short help text for each property and event.

  • The DesignerSerializationVisibility and PersistenceMode attributes are used to specify whether and how a property is persisted into the .aspx file.

  • The DefaultValue attribute allows the property browser to emphasize properties that have been changed from their default state and enables the control persister to determine which properties need to be persisted into the .aspx file.

  • The Browsable attribute is used to specify whether a property is made visible in the designer's property browser.

Designer Implementation

The DatePicker control has an associated designer that allows the control to cleanly separate design time “specific functionality from the runtime control implementation and provide this functionality through a different class used only at design time. We discussed control designers in Chapter 15.

Listing 21-10 illustrates the implementation of a simple control designer for the DatePicker control.

Listing 21-10 Implementation of the designer
  namespaceMSPress.WebControls.Design  {   publicclassDatePickerDesigner:ControlDesigner  { privatestaticstringClientFilePathPrefix;   publicoverridestringGetDesignTimeHtml()  { //Thedesignercanassumeknowledgeoftheruntimecontrol //becausetheygohandinhand.Therefore,theDatePicker //designercanindexintotheControlscollection.  ControlCollectionchildControls= (DatePicker)Component).Controls;  Debug.Assert(childControls.Count==3); Debug.Assert(childControls[1]isImage); Debug.Assert(childControls[2]is RegularExpressionValidator); //ChangingtheURLofthepickerimagetothefile //locationisspecifictothedesigner;thus,itis //donehere. 
 ImagepickerImage=(Image)childControls[1]; pickerImage.ImageUrl= "file:///"+GetClientFilePath("Picker.gif"); //Thevalidatorshouldnotshowupindesignview;thus, //fordesign-timescenarios,itsvisibilityissetto //false. RegularExpressionValidatorvalidator= (RegularExpressionValidator)childControls[2]; validator.Visible=false; returnbase.GetDesignTimeHtml(); }  publicoverridevoidInitialize(IComponentcomponent)  { //Checkcorrectusageofthedesignerinotherwords, //thatithasbeenassociatedwiththerighttypeof //runtimecontrol-sothatalltheassumptionsadesigner //makeswillhold. if(!(componentisDatePicker)){ thrownewArgumentException("ComponentmustbeaDatePicker","component"); } base.Initialize(component); } } } 

The DatePickerDesigner follows the general guidelines related to designers and is implemented in a Design subnamespace of the MSPress.WebControls namespace that contains the control. Furthermore, the designer class is named by appending the word Designer to the control's class name.

The DatePickerDesigner overrides the Initialize method from the Control ­Designer class to verify that the component it is being associated with is an instance of a DatePicker . This check allows the designer implementation to safely make assumptions about the runtime control it is associated with.

The DatePickerDesigner overrides the GetDesignTimeHtml method from the ControlDesigner class to customize the runtime control before it is rendered to produce HTML that is shown on a visual design surface. In particular, the designer hides the RegularExpressionValidator contained inside the DatePicker . The designer also resets the ImageUrl property of the Image control contained inside the DatePicker with a file:/// -based URL. The designer makes this setting so that the image can be successfully loaded by the design surface and the control provides a WYSIWYG experience.

The DatePickerDesigner also retrieves the Controls collection of its associated runtime control. All designers associated with composite controls should do this. As shown in Listing 21-2, the DatePicker overrides its Controls property to ensure a valid Controls collection. The designer calls this property, which ensures a valid collection of controls that is then rendered to produce a non-empty design-time appearance. Figure 21-5 shows the DatePicker control used in the Web Forms designer.

Figure 21-5. The DatePicker used in the Web Forms designer inside Visual Studio .NET

graphics/f21hn05.jpg



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net