Properties and Type Converters


The metadata attributes that we described in the previous section represent one piece of the architecture that enables declarative property persistence. The other piece consists of type converter classes that perform conversions to and from the given type to the String type and to other types. When a property is specified declaratively , as Width="100px" is, the page parser invokes an instance of a type converter class to convert the string value to the declared type of the property. For example, when parsing Width="100px" , the parser invokes the System.Web.UI.WebControls.UnitConverter to convert the string "100px" to the System.Web.UI.WebControls.Unit type, which can be assigned to the Width property.

A type converter is a class that derives from the System.ComponentModel.TypeConverter class, which provides methods that perform conversions to and from other types to the given type. In the previous paragraph, we showed an example of how the page parser uses type converters. The page framework also uses type converters to perform view state serialization. And visual designers use type converters to display properties in the property browser and to perform design-time serialization.

Primitive types and many other types in the .NET Framework class library have type converters associated with them. For example, the Boolean , Char , Enum , and Int32 types have corresponding BooleanConverter , CharConverter , EnumConverter , and Int32Converter classes associated with them.

To associate a type converter with a type, you apply the TypeConverter ­Attribute to the type. The following code fragment associates the System.Web.UI. WebControls.UnitConverter with the System.Web.UI.WebControls.Unit type:

 [ TypeConverter(typeof(UnitConverter)) ] publicstructUnit{...} 

When a type converter is associated with a type, it is automatically available to properties of that type. If you want to associate a different type converter with a property of that type, you can override the property and specify another type converter via the TypeConverterAttribute . The following example shows how the WebControl class implements the BackColor property to associate a different type converter from the default ColorConverter associated with the Color type:

 [ TypeConverterAttribute(typeof(WebColorConverter)) ] publicvirtualColorBackColor{...} 

When the type of a property does not have a type converter associated with it, you can associate a type converter with the property by applying TypeConverterAttribute to the property. If you define custom types for properties, you must also implement type converters and associate them with the types. We'll next show how to implement a type converter class.

Implementing a Type Converter

Let's define a few custom types and their associated type converters. We will use these types in the MapDemo example later in this section. The MapDemo control is a simple abstraction over the HTML <map> element that allows a page developer to specify circular and rectangular regions that are activated when the user clicks within an image. The custom types that we will define represent geometrical entities such as a point, circle, or rectangle. Figure 10-1 shows the custom types displayed in the property browser.

Figure 10-1. The Circle and Rectangle properties of the MapDemo control displayed in the property browser

graphics/f10hn01.jpg

The MapDemo control exposes Circle and Rectangle properties whose types are the MapCircle and MapRectangle custom types:

 publicclassMapDemo:Image,IPostBackEventHandler{ publicMapCircleCircle{...} publicMapRectangleRectangle{...} } 

The properties of the MapCircle and MapRectangle classes are subproperties of the Circle and Rectangle properties of the MapDemo class:

 publicclassMapCircle{ publicMapPointOrigin{...} publicintRadius{...} } publicclassMapRectangle{ publicMapPointTopLeft{...} publicMapPointBottomRight{...} } 

Here's how a type converter comes into the picture. The MapCircle and MapRectangle custom types define properties of another custom type: MapPoint . When the MapDemo control is used declaratively on a page ”as shown in the following example ”the page parser uses a type converter to convert the specified string values of the subproperties into the corresponding MapPoint types:

 <msp:MapDemorunat="server"Circle-Origin="100,50"Circle-Radius="50"> <RectangleTopLeft="0,100"BottomRight="200,150"></Rectangle> </msp:MapDemo> 

In addition, the property browser uses type converters to enable editing of these properties, and the designer uses them to serialize these properties.

Now let's look at the implementation of MapPoint and its associated type converter.

MapPoint and MapPointConverter

Listing 10-1 contains the code for the MapPoint class. The type converter associated with the MapPoint type is shown in Listing 10-2.

Listing 10-1 MapPoint.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Globalization; namespaceMSPress.ServerControls{ [ TypeConverter(typeof(MapPointConverter)) ] publicclassMapPoint{ privateint_x; privateint_y; publicMapPoint():this(0,0){ } publicMapPoint(intx,inty){ _x=x; _y=y; } publicboolIsEmpty{ get{ return(_x==0&&_y==0); } } publicintX{ get{ return_x; } set{ _x=value; } } 
 publicintY{ get{ return_y; } set{ _y=value; } } publicoverrideboolEquals(objectobj){ MapPointother=objasMapPoint; if(other!=null){ return(other.X==X)&&(other.Y==Y); } returnfalse; } publicoverrideintGetHashCode(){ return_x.GetHashCode()^_y.GetHashCode(); } publicoverridestringToString(){ returnToString(CultureInfo.CurrentCulture); } publicvirtualstringToString(CultureInfoculture){ returnTypeDescriptor.GetConverter(typeof(MapPoint)).ConvertToString(null,culture,this); } } } 

The MapPointConverter class in Listing 10-2 shows the essential aspects of implementing a type converter that is needed for a read-write property.

Listing 10-2 MapPointConverter.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design.Serialization; usingSystem.Globalization; usingSystem.Reflection; 
 namespaceMSPress.ServerControls{ publicclassMapPointConverter:TypeConverter{ publicoverrideboolCanConvertFrom(ITypeDescriptorContextcontext,TypesourceType){ if(sourceType==typeof(string)){ returntrue; } returnbase.CanConvertFrom(context,sourceType); } publicoverrideboolCanConvertTo(ITypeDescriptorContextcontext,TypedestinationType){ if((destinationType==typeof(string)) (destinationType==typeof(InstanceDescriptor))){ returntrue; } returnbase.CanConvertTo(context,destinationType); } publicoverrideobjectConvertFrom(ITypeDescriptorContextcontext,CultureInfoculture, objectvalue){ if(value==null){ returnnewMapPoint(); } if(valueisstring){ strings=(string)value; if(s.Length==0){ returnnewMapPoint(); } string[]parts= s.Split(culture.TextInfo.ListSeparator[0]); if(parts.Length!=2){ thrownewArgumentException("InvalidMapPoint", "value"); } TypeConverterintConverter= TypeDescriptor.GetConverter(typeof(Int32)); returnnewMapPoint((int)intConverter.ConvertFromString(context,culture,parts[0]), (int)intConverter.ConvertFromString(context,culture,parts[1])); } returnbase.ConvertFrom(context,culture,value); } publicoverrideobjectConvertTo(ITypeDescriptorContextcontext,CultureInfoculture, objectvalue,TypedestinationType){ if(value!=null){ if(!(valueisMapPoint)){ thrownewArgumentException("InvalidMapPoint", "value"); } } if(destinationType==typeof(string)){ if(value==null){ returnString.Empty; } MapPointpoint=(MapPoint)value; TypeConverterintConverter= TypeDescriptor.GetConverter(typeof(Int32)); returnString.Join(culture.TextInfo.ListSeparator, newstring[]{ intConverter.ConvertToString(context,culture, point.X), intConverter.ConvertToString(context,culture, point.Y) }); } elseif(destinationType==typeof(InstanceDescriptor)){ if(value==null){ returnnull; } MemberInfomi=null; object[]args=null; MapPointpoint=(MapPoint)value; if(point.IsEmpty){ mi=typeof(MapPoint).GetConstructor(newType[0]); } else{ TypeintType=typeof(int); mi=typeof(MapPoint).GetConstructor(newType[]{intType,intType}); args=newobject[]{point.X,point.Y}; } if(mi!=null){ returnnewInstanceDescriptor(mi,args); } else{ returnnull; } } returnbase.ConvertTo(context,culture,value, destinationType); } } } 

MapPointConverter performs string-to-value conversions and generates an abstract representation of a constructor that creates an instance of the MapPoint type. It implements the following logic:

  • Derives from System.ComponentModel.TypeConverter .

  • Overrides the CanConvertFrom method of the base class to specify that it can convert from a String type to a MapPoint type.

  • Overrides the CanConvertTo method of the base class to specify that it can convert a MapPoint type to a String type as well as to a System.Com ­ponentModel.Design.Serialization.InstanceDescriptor type. The conversion to an InstanceDescriptor is required when a property is read-write and the parser needs to generate code that creates an instance of the type.

  • Overrides the ConvertFrom method of the base class to take a String value, parses the value, and creates and returns a MapPoint instance.

  • Overrides the ConvertTo method of the base class to take a MapPoint instance and, based on the destination type requested , returns either a String object or an InstanceDescriptor object. The InstanceDescriptor object provides information about the constructor used to create the MapPoint instance passed into the ConvertTo method. This information is used by the parser to generate code that creates an instance of the MapPoint type.

Type Converters for the MapCircle and MapRectangle Types

Before looking at the MapCircle and MapRectangle types, we will define an abstract MapShape base class that expresses the common characteristics of a shape. We will also define a MapShapeConverter class, which derives from ExpandableObjectConverter and enables hierarchical UI for subproperties in the property browser. We will associate the MapShapeConverter with the MapShape base class via a type converter attribute. Listing 10-3 contains the code for the MapShape class.

Listing 10-3 MapShape.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Globalization; namespaceMSPress.ServerControls{ publicabstractclassMapShapeConverter: ExpandableObjectConverter{} [TypeConverter(typeof(MapShapeConverter))] publicabstractclassMapShape{ [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] publicabstractboolIsEmpty{ get; } [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] publicabstractstringMapCoordinates{ get; } 
 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] protectedabstractMapShapeNameMapShapeName{ get; } [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] publicstringShapeName{ get{ returnMapShapeName.ToString().ToLower(CultureInfo.InvariantCulture); } } publicabstractvoidLoadFromString(stringvalue); publicabstractstringSaveToString(); publicoverridestringToString(){ returnToString(CultureInfo.InvariantCulture); } publicvirtualstringToString(CultureInfoculture){ returnTypeDescriptor.GetConverter(GetType()).ConvertToString(null,culture,this); } } } 

Listing 10-4 shows the MapShapeName enumeration, which contains the shape names allowed by the HTML map specification: circle, rectangle, and polygon.

Listing 10-4 MapShapeName.cs
 usingSystem; namespaceMSPress.ServerControls{ publicenumMapShapeName{ Circle, Rectangle, Polygon } } 

The MapCircle and MapRectangle classes derive from the MapShape class. You'll find the complete code for the MapCircle and MapRectangle classes in this book's sample files. We have not defined a MapPolygon type, but you can extend the samples in this chapter by defining and using this type. The code fragments in Listing 10-5 show the definition of properties in the MapCircle and MapRectangle classes:

Listing 10-5 Property definitions in the MapCircle and MapRectangle classes
 [ TypeConverter(typeof(MapCircleConverter)) ] publicclassMapCircle:MapShape{ [ DefaultValue(typeof(MapPoint),""), NotifyParentProperty(true) ] publicMapPointOrigin{ get{ return_origin; } set{ if(value==null){ thrownewArgumentNullException("value"); } _origin=value; } } [ DefaultValue(0), NotifyParentProperty(true) ] publicintRadius{ get{ return_radius; } set{ if(value<0){ thrownewArgumentOutOfRangeException("value"); } _radius=value; } }  } 
 [ TypeConverter(typeof(MapRectangleConverter)) ] publicclassMapRectangle:MapShape{ [ DefaultValue(typeof(MapPoint),""), NotifyParentProperty(true) ] publicMapPointBottomRight{ get{ return_bottomRight; } set{ if(value==null){ thrownewArgumentNullException("value"); } _bottomRight=value; } } [ DefaultValue(typeof(MapPoint),""), NotifyParentProperty(true) ] publicMapPointTopLeft{ get{ return_topLeft; } set{ if(value==null){ thrownewArgumentNullException("value"); } _topLeft=value; } }  } 

We'll now examine the type converters for the MapCircle and MapRectangle types. When a server control defines properties of these types (and any other complex type), they should be defined as read-only properties because of state management, as we'll explain in the next section. When a property is read-only, a page parser does not require a type converter for the type of the property. However, a designer needs a type converter for a complex property that will be displayed in the property browser. The MapDemo control, which we'll look at shortly, illustrates this point. MapDemo contains the Circle property and the Rectangle property, whose respective types are MapCircle and MapRectangle . These properties are read-only, and property syntax such as Circle="100,50,50" is not allowed. Therefore, the page parser never has to convert from string representations to instances of the MapCircle or MapRectangle types. However, the designer needs type converters for these types because it uses them to display the string representations of the properties in the property browser. Figure 10-1 shows how the Circle and Rectangle properties of the MapDemo control are displayed in the property browser in Microsoft Visual Studio .NET.

Listing 10-6 contains the code for the MapCircleConverter class. The implementation of MapRectangleConverter is similar to that of MapCircleConverter and is provided in the book's sample files. The type converter implementation in these cases is simpler than that of MapPointConverter because MapCircleConverter and MapRectangleConverter do not have to convert to an InstanceDescriptor . As we described earlier in the MapPointConverter example, an InstanceDescriptor is needed only if the type converter is used to generate code that creates an instance of the type, which is not needed for a read-only property.

Listing 10-6 MapCircleConverter.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design.Serialization; usingSystem.Globalization; usingSystem.Reflection; namespaceMSPress.ServerControls{ publicclassMapCircleConverter:MapShapeConverter{ publicoverrideboolCanConvertFrom(ITypeDescriptorContextcontext,TypesourceType){ if(sourceType==typeof(string)){ returntrue; } returnbase.CanConvertFrom(context,sourceType); } publicoverrideboolCanConvertTo(ITypeDescriptorContextcontext,TypedestinationType){ if(destinationType==typeof(string)){ returntrue; } returnbase.CanConvertTo(context,destinationType); } 
 publicoverrideobjectConvertFrom(ITypeDescriptorContextcontext,CultureInfoculture, objectvalue){ if(value==null){ returnnewMapCircle(); } if(valueisstring){ strings=(string)value; if(s.Length==0){ returnnewMapCircle(); } string[]parts= s.Split(culture.TextInfo.ListSeparator[0]); if(parts.Length!=3){ thrownewArgumentException("InvalidMapCircle", "value"); } TypeConverterintConverter= TypeDescriptor.GetConverter(typeof(Int32)); returnnewMapCircle(newMapPoint((int)intConverter.ConvertFromString(context,culture,parts[0]), (int)intConverter.ConvertFromString(context, culture,parts[1])), (int)intConverter.ConvertFromString(context, culture,parts[2])); } returnbase.ConvertFrom(context,culture,value); } publicoverrideobjectConvertTo(ITypeDescriptorContextcontext,CultureInfoculture, objectvalue,TypedestinationType){ if(value!=null){ if(!(valueisMapCircle)){ thrownewArgumentException("InvalidMapCircle", "value"); } } if(destinationType==typeof(string)){ if(value==null){ returnString.Empty; } MapCirclecircle=(MapCircle)value; if(circle.IsEmpty){ returnString.Empty; } TypeConverterintConverter= TypeDescriptor.GetConverter(typeof(Int32)); returnString.Join(culture.TextInfo.ListSeparator, newstring[]{ intConverter.ConvertToString(context,culture, circle.Origin.X), intConverter.ConvertToString(context,culture, circle.Origin.Y), intConverter.ConvertToString(context,culture, circle.Radius) }); } returnbase.ConvertTo(context,culture,value, destinationType); } } } 

Putting It Together ”The MapDemo Example

We'll now use the MapPoint , MapCircle , and MapRectangle custom types and their associated type converters to implement a MapDemo control. The MapDemo control allows a page developer to create a circular region and a rectangular region (hot spots) in an image that cause postback to the server when a user clicks inside them. MapDemo is a trimmed -down version of the ImageMap control that we will implement later in this chapter. Figure 10-2 shows a page that uses the MapDemo control.

Figure 10-2. MapDemoTest.aspx viewed in a browser. The rendered image has an associated HTML map with two hot spots.

graphics/f10hn02.jpg

The MapDemo control, shown in Listing 10-7, derives from the ASP.NET Image control and adds to it the functionality to render an image map.

Listing 10-7 MapDemo.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ [ DefaultEvent("MapClick"), DefaultProperty("Rectangle") ] publicclassMapDemo:Image,IPostBackEventHandler{ privatestaticreadonlyobjectEventMapClick=newobject(); privateconststringMapCircleArg="circle"; privateconststringMapRectangleArg="rectangle"; privateMapCircle_circle; privateMapRectangle_rectangle; privatebool_hasHotSpots; 
 [ Category("Shape"), DefaultValue(typeof(MapCircle),""), Description("Thelocationofthecircularhotspot"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true) ] publicMapCircleCircle{ get{ if(_circle==null){ _circle=newMapCircle(); } return_circle; } } [ Category("Shape"), DefaultValue(""), Description("Thelocationoftherectangularhotspot"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), PersistenceMode(PersistenceMode.InnerProperty), NotifyParentProperty(true) ] publicMapRectangleRectangle{ get{ if(_rectangle==null){ _rectangle=newMapRectangle(); } return_rectangle; } } [ Category("Action"), Description("Raisedwhenahotspotisclicked") ] publiceventMapClickEventHandlerMapClick{ add{ Events.AddHandler(EventMapClick,value); } remove{ Events.RemoveHandler(EventMapClick,value); } } protectedoverridevoidAddAttributesToRender(HtmlTextWriterwriter){ base.AddAttributesToRender(writer); if(_hasHotSpots){ writer.AddAttribute("UseMap","#ImageMap"+ClientID, false); } } protectedvirtualvoidOnMapClick(MapClickEventArgse){ MapClickEventHandlermapClickHandler= (MapClickEventHandler)Events[EventMapClick]; if(mapClickHandler!=null){ mapClickHandler(this,e); } } protectedoverridevoidRender(HtmlTextWriterwriter){ if(((_circle!=null)&&(_circle.IsEmpty==false)) ((_rectangle!=null)&& (_rectangle.IsEmpty==false))){ _hasHotSpots=true; } base.Render(writer); if(_hasHotSpots){ writer.AddAttribute(HtmlTextWriterAttribute.Name, "ImageMap"+ClientID); writer.RenderBeginTag(HtmlTextWriterTag.Map); if((_circle!=null)&&(_circle.IsEmpty==false)){ writer.AddAttribute("shape",_circle.ShapeName, false); writer.AddAttribute("coords", _circle.MapCoordinates); writer.AddAttribute(HtmlTextWriterAttribute.Href, Page.GetPostBackClientHyperlink(this, MapCircleArg)); writer.AddAttribute(HtmlTextWriterAttribute.Title, "CircularHotspot",false); writer.RenderBeginTag("area"); writer.RenderEndTag(); } if((_rectangle!=null)&& (_rectangle.IsEmpty==false)){ writer.AddAttribute("shape",_rectangle.ShapeName, false); writer.AddAttribute("coords", _rectangle.MapCoordinates,false); writer.AddAttribute(HtmlTextWriterAttribute.Href, Page.GetPostBackClientHyperlink(this, MapRectangleArg)); writer.AddAttribute(HtmlTextWriterAttribute.Title, "RectangularHotspot",false); writer.RenderBeginTag("area"); writer.RenderEndTag(); } writer.RenderEndTag();//Map } } #regionCustomStateManagementImplementation protectedoverridevoidLoadViewState(objectsavedState){ base.LoadViewState(savedState); strings=null; s=(string)ViewState["Circle"]; if(s!=null){ Circle.LoadFromString(s); } s=(string)ViewState["Rectangle"]; if(s!=null){ Rectangle.LoadFromString(s); } } protectedoverrideobjectSaveViewState(){ stringcurrentState=null; stringinitialState=null; if(_circle!=null){ currentState=_circle.SaveToString(); initialState=(string)ViewState["Circle"]; if(currentState.Equals(initialState)==false){ ViewState["Circle"]=currentState; } } if(_rectangle!=null){ currentState=_rectangle.SaveToString(); initialState=(string)ViewState["Rectangle"]; if(currentState.Equals(initialState)==false){ ViewState["Rectangle"]=currentState; } } returnbase.SaveViewState(); } protectedoverridevoidTrackViewState(){ if(_circle!=null){ ViewState["Circle"]=_circle.SaveToString(); } if(_rectangle!=null){ ViewState["Rectangle"]=_rectangle.SaveToString(); } base.TrackViewState(); } #endregionCustomStateManagementImplementation #regionImplementationofIPostBackEventHandler voidIPostBackEventHandler.RaisePostBackEvent(stringeventArg){ strings=string.Empty; if(eventArg!=null){ if(eventArg.Equals(MapCircleArg)) s=MapCircleArg; elseif(eventArg.Equals(MapRectangleArg)) s=MapRectangleArg; } OnMapClick(newMapClickEventArgs(s)); } #endregionImplementationofIPostBackEventHandler } } 

Listings 10-8 and 10-9 contain the code for the MapClickEventArgs and MapClickEventHandler classes associated with the MapClick event.

Listing 10-8 MapClickEventArgs.cs
 usingSystem; namespaceMSPress.ServerControls{ publicclassMapClickEventArgs:EventArgs{ privatestring_action; publicMapClickEventArgs(stringaction){ _action=action; } publicstringAction{ get{ return_action; } } } } 
Listing 10-9 MapClickEventHandler.cs
 usingSystem; namespaceMSPress.ServerControls{ publicdelegatevoidMapClickEventHandler(objectsender, MapClickEventArgse); } 

MapDemo illustrates the following points:

  • Design-time attributes for complex properties

    MapDemo defines the Circle and Rectangle properties of the custom types Map ­Circle and MapRectangle . The DesignerSerializationVisibility(Content) attribute on these properties tells the designer that their content (subproperties) should be serialized. To illustrate various forms of persistence for complex properties, MapDemo enables persistence of the Circle property on the control's tag and enables persistence of the Rectangle property as an inner property. The PersistenceMode(PersistenceMode.InnerProperty) attribute on the Rectangle property tells the designer that this property should be persisted as an inner property. The Circle property is not marked with the PersistenceModeAttribute ; therefore, the designer persists it on the control's tag, which is the default persistence mode.

    In general, you should pick a single model of persistence and use that model consistently. The NotifyParentProperty(true) attributes on the Circle and Rectangle properties cause changes in their subproperties to be persisted into the page. Note that design-time attributes are not used by the page parser. If you use the MapDemo control in a page that is created without the help of a visual designer, you will be able to specify each of the Circle and Rectangle properties on the tag or as an inner property. Persistence on the control's tag is always allowed by the page parser, and inner persistence is possible because MapDemo derives from WebControl , which is marked as ParseChildren(true) .

  • Complex properties should be read-only

    The Circle and Rectangle properties of MapDemo are read-only. In general, complex properties that require state management should be defined as read-only properties. This allows a control to have complete responsibility for creating and handing out an instance of the type that represents the property and for managing the state of the property.

  • State management

    MapDemo implements state management by overriding the TrackViewState , SaveViewState , and LoadViewState methods it inherits from the Control class. When overriding these methods, the order of invocation of the methods of the base class is important.

    In TrackViewState , MapDemo saves a string representation of the Circle and Rectangle properties in its ViewState dictionary before invoking the TrackViewState method of the base class. This ensures that the initial values are not marked dirty.

    In SaveViewState , MapDemo compares the current values of the string representations of the Circle and Rectangle properties with those initially saved in ViewState . If the values differ , MapDemo stores the new values in ViewState . Because property tracking is on during this phase, the values saved in ViewState are automatically marked dirty. MapDemo next invokes the SaveViewState method of the base class.

    In LoadViewState , MapDemo first invokes the LoadViewState method of the base class so that the ViewState dictionary is restored after postback. MapDemo then checks whether there are values in ViewState corresponding to the Circle and Rectangle properties. If MapDemo finds saved values, it uses them to load state into these properties.

  • Rendering

    MapDemo overrides the AddAttributesToRender method of the base class to render the usemap attribute on the HTML < img > tag rendered by the base Image control. MapDemo overrides the Render method to render data for the HTML < map> element after it invokes the Render method of the base class to render the image.

  • Implementing IPostBackEventHandler

    MapDemo renders client-side script to cause postback (as shown by the NavButtons control in Chapter 9, "Control Life Cycle, Events, and Postback") when a user clicks a hot spot (image map region) in the browser. MapDemo implements IPostBackEventHandler to capture the postback event caused by clicking a hot spot and maps it to a server-side MapClick event. Lisings 10-8 and 10-9 contain the code for the event data and event delegate classes associated with the MapClick event: MapClickEventArgs and MapClickEventHandler .

Listing 10-10 shows the MapDemoTest.aspx page that uses the MapDemo control. The Circle property is persisted on the control's tag by using the hyphenated syntax for subproperties, and the Rectangle property is persisted as an inner property. Figure 10-2 shows the page accessed in a browser.

Listing 10-10 MapDemoTest.aspx
 <%@PageLanguage="C#"%> <%@RegisterTagPrefix="msp"Namespace="MSPress.ServerControls" Assembly="MSPress.ServerControls"%> <html> <head> <scriptrunat="server"> voidmapDemo1_MapClick(objectsender,MapClickEventArgse){ label1.Text= string.Format("Youclickedthe{0}.",e.Action); } </script> </head> <body> <formrunat="server"> <br> Clickashape: <msp:MapDemoImageUrl="Shapes.gif"ImageAlign="Middle" runat="server"OnMapClick="mapDemo1_MapClick"id="mapDemo1" Circle-Origin="100,50"Circle-Radius="50"> <RectangleBottomRight="200,150"TopLeft="0,100"></Rectangle> </msp:MapDemo> <asp:LabelFont-Size="10pt"Font-Bold="true"id="label1" runat="server"/> </form> </body> </html> 

If you view the source for the page rendered in your browser, you will see the <map> element in the HTML rendered by the MapDemo instance:

 <imgid="mapDemo1"src="/BookWeb/Chapter10/Shapes.gif" align="Middle"border="0"UseMap="#ImageMapmapDemo1"/> <mapname="ImageMapmapDemo1"> <areashape="circle"coords="100,50,50" href="javascript:__doPostBack('mapDemo1','circle')" title="CircularHotSpot"> </area> <areashape="rectangle"coords="0,100,200,150" href="javascript:__doPostBack('mapDemo1','rectangle')" title="RectangularHotSpot"> </area> </map> 


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