Collection Properties The ImageMap Example


Collection Properties ”The ImageMap Example

We'll now implement an ImageMap control that has a HotSpots collection property that contains the CircleHotSpot and RectangleHotSpot custom types we defined in the previous section. The HotSpots property allows a page developer to specify an arbitrary number of circular and rectangular areas in an image, which ImageMap renders as regions of an HTML image map. The ImageMap control has two modes: navigation , in which each hot spot causes navigation to its specified URL, and postback , in which each hot spot causes postback to the page. Figure 10-3 shows the ImageMap control used in navigation mode. When the user's cursor rests in a map region (hot spot), the browser displays a tool tip over the hot spot and shows the URL for the hot spot in the status bar. When the user clicks a hot spot, the browser navigates to the specified URL.

Figure 10-3. The ImageMapTest.aspx page viewed in a browser. The image is an HTML map generated by an ImageMap control instance used in navigation mode.

graphics/f10hn03.jpg

Listing 10-13 shows the ImageMapTest.aspx page. The page demonstrates inner default property persistence, whereby the content nested within the ImageMap control's tags corresponds to items of the HotSpots inner default property. Although rectangular areas are the most intuitive map regions for the image shown in Figure 10-3, we have added a few circular hot spots in the page to demonstrate that the HotSpots property can contain both CircleHotSpot and RectangleHotSpot objects. If you want to see the view state information for the control tree, turn on page tracing by adding the Trace="true" attribute to the Page directive. You will see that there is no view state for the ImageMap instance in the ImageMapTest.aspx page. However, if you change any properties of ImageMap after initialization ”for example, in the Page_Load event handler ”you will see view state for the control displayed in the trace output for the control tree.

Listing 10-13 ImageMapTest.aspx
 <%@PageLanguage="C#"%> <%@RegisterTagPrefix="msp"Namespace="MSPress.ServerControls" Assembly="MSPress.ServerControls"%> <html> <body> 
 <formrunat="server"> <p> ClickanareaoftheclasslibrarytonavigatetoitsHelppage: </p> <p> <msp:ImageMapImageUrl="ClassLibrary.jpg"Mode="Navigation" ImageAlign="Middle"Runat="server"id="imageMap1"> <msp:RectangleHotSpotToolTip="ClickforASP.NETHelpPage" BottomRight="270,159" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpconcreatingaspwebapplications.asp?frame=true" TopLeft="11,14"> </msp:RectangleHotSpot> <msp:RectangleHotSpot ToolTip="ClickforWindowsFormsHelpPage" BottomRight="464,158"Action="http://www.msdn.microsoft.com /library/en-us/vbcon/html/ vboriCreatingStandaloneAppsVB.asp?frame=true" TopLeft="278,15"> </msp:RectangleHotSpot> <msp:RectangleHotSpot ToolTip="ClickforComponentModelHelpPage" BottomRight="464,212" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpconcomponentprogrammingessentials.asp?frame=true" TopLeft="13,169"> </msp:RectangleHotSpot> <msp:CircleHotSpotToolTip="ClickforADO.NETHelpPage" Radius="50"Origin="87,269" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpconaccessingdatawithadonet.asp?frame=true"> </msp:CircleHotSpot> <msp:CircleHotSpotToolTip="ClickforXMLHelpPage" Radius="50"Origin="237,269" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpconemployingxmlinnetframework.asp?frame=true"> </msp:CircleHotSpot> <msp:CircleHotSpotToolTip="ClickforGDI+HelpPage" Radius="50"Origin="388,269" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpcondrawingeditingimages.asp?frame=true"> </msp:CircleHotSpot> <msp:RectangleHotSpot ToolTip="ClickforCommonLanguageRuntimeHelpPage" BottomRight="477,452" Action="http://www.msdn.microsoft.com/library/en-us/cpguide /html/cpconthecommonlanguageruntime.asp?frame=true" TopLeft="1,401"> </msp:RectangleHotSpot> </msp:ImageMap> </p> </form> </body> </html> 

Before we show the code for the ImageMap class, we'll demonstrate a design-time feature of collection properties. In the property browser, a collection property is displayed in the same manner as the HotSpots property shown in Figure 10-4.

Figure 10-4. Properties of the ImageMap instance, as displayed in the Visual Studio property browser. Collection properties are always displayed the same way as the HotSpots property.

graphics/f10hn04.jpg

When the page developer clicks the ellipsis shown beside the HotSpots collection property in Figure 10-4, the property browser will provide a collection editor UI such as that shown in Figure 10-5. To enable support for collection editing, you have to associate a collection editor with a collection. (We will describe collection editors in greater detail in Chapter 15, "Design-Time Functionality.") A collection editor provides a user interface that allows a page developer to easily add or remove collection items, edit a collection item, and move an item up or down in the list.

Figure 10-5. Collection editor for the HotSpots property of the ImageMap control

graphics/f10hn05.jpg

Listing 10-14 contains the code for the ImageMap control, which is similar to the MapDemo control we described in Listing 10-7. The most significant feature of Image M ap is that it defines a HotSpots collection property and implements custom state management related to this property.

Listing 10-14 ImageMap.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ [ DefaultEvent("MapClick"), DefaultProperty("HotSpots"), ParseChildren(true,"HotSpots") ] publicclassImageMap:Image,IPostBackEventHandler{ privatestaticreadonlyobjectEventMapClick=newobject(); privatebool_hasHotSpots; privateHotSpotCollection_hotSpots; [ Category("Behavior"), Description("Thecollectionofhotspots"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), 
 PersistenceMode(PersistenceMode.InnerDefaultProperty) ] publicHotSpotCollectionHotSpots{ get{ if(_hotSpots==null){ _hotSpots=newHotSpotCollection(); if(IsTrackingViewState){ ((IStateManager)_hotSpots).TrackViewState(); } } return_hotSpots; } } [ Category("Behavior"), DefaultValue(ImageMapMode.Navigation), Description("Specifieswhethertheimagemapcausespostback"+ "ornavigation."), ] publicImageMapModeMode{ get{ objectobj=ViewState["Mode"]; return(obj==null)? ImageMapMode.Navigation:(ImageMapMode)obj; } set{ if(value<ImageMapMode.Navigation value>ImageMapMode.Postback){ thrownewArgumentOutOfRangeException("value"); } ViewState["Mode"]=value; } } [ 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){ _hasHotSpots= ((_hotSpots!=null)&&(_hotSpots.Count>0)); base.Render(writer); if(_hasHotSpots){ writer.AddAttribute(HtmlTextWriterAttribute.Name, "ImageMap"+ClientID); writer.RenderBeginTag(HtmlTextWriterTag.Map); ImageMapModemode=Mode; inthotSpotIndex=0; foreach(HotSpotitemin_hotSpots){ writer.AddAttribute("shape",item.Shape.ShapeName, false); writer.AddAttribute("coords", item.Shape.MapCoordinates); if(mode==ImageMapMode.Postback){ writer.AddAttribute(HtmlTextWriterAttribute.Href, Page.GetPostBackClientHyperlink(this, hotSpotIndex.ToString())); } else{ writer.AddAttribute(HtmlTextWriterAttribute.Href,item.Action); } writer.AddAttribute(HtmlTextWriterAttribute.Title, item.ToolTip,false); writer.RenderBeginTag("area"); writer.RenderEndTag(); ++hotSpotIndex; } writer.RenderEndTag();//Map } } #regionCustomStateManagementImplementation protectedoverridevoidLoadViewState(objectsavedState){ objectbaseState=null; object[]myState=null; if(savedState!=null){ myState=(object[])savedState; if(myState.Length!=2){ thrownewArgumentException("Invalidviewstate"); } baseState=myState[0]; } base.LoadViewState(baseState); if((myState!=null)&&(myState[1]!=null)){ ((IStateManager)HotSpots).LoadViewState(myState[1]); } } protectedoverrideobjectSaveViewState(){ objectbaseState=base.SaveViewState(); objecthotSpotsState=null; if((_hotSpots!=null)&&(_hotSpots.Count>0)){ hotSpotsState= ((IStateManager)_hotSpots).SaveViewState(); } if((baseState!=null)(hotSpotsState!=null)){ object[]savedState=newobject[2]; savedState[0]=baseState; savedState[1]=hotSpotsState; returnsavedState; } returnnull; } protectedoverridevoidTrackViewState(){ base.TrackViewState(); if(_hotSpots!=null){ ((IStateManager)_hotSpots).TrackViewState(); } } #endregionCustomStateManagementImplementation #regionImplementationofIPostBackEventHandler voidIPostBackEventHandler.RaisePostBackEvent(stringeventArg){ if(eventArg!=null){ inthotSpotIndex=Int32.Parse(eventArg); stringaction=_hotSpots[hotSpotIndex].Action; OnMapClick(newMapClickEventArgs(action)); } } #endregionImplementationofIPostBackEventHandler } } 

Listing 10-15 shows the ImageMapMode enumeration.

Listing 10-15 ImageMapMode.cs
 usingSystem; namespaceMSPress.ServerControls{ publicenumImageMapMode{ Navigation, Postback } } 

There are several points to note regarding the ImageMap control:

  • ImageMap is marked with the ParseChildren(true, "HotSpots") attribute, which tells the page parser that HotSpots is the inner default property for the control.

  • The HotSpots property is marked with the PersistenceMode(PersistenceMode.InnerDefaultPropert y) attribute, which tells the designer to persist the HotSpots property as the inner default property for the control. This causes the designer to persist the items of the HotSpots collection within the control's tags.

  • ImageMap defines a Mode property that allows a page developer to specify whether the hot spots cause navigation or postback. The type of the Mode property is the ImageMapMode enumeration, which is shown in Listing 10-15.

  • ImageMap performs custom state management by overriding the TrackViewState , SaveViewState , and LoadViewState methods of the base class. The HotSpotCollection class performs its own state management, as we will soon describe. In each of its state management methods, ImageMap first invokes the relevant method of the base class and then invokes the corresponding method of its HotSpots property. The view state of the ImageMap control has two parts : the view state of the base class, and the view state of the HotSpots collection. Note that the SaveViewState method could return a Pair object because the view state has two parts. However, this method returns an array of objects to demonstrate a technique that you can use in a more general scenario.

  • ImageMap cycles through the HotSpots collection to render each hot spot. If ImageMap is used in navigation mode, it renders the navigation URL for each HotSpot object. If ImageMap is used in postback mode, it renders JavaScript that causes postback for each hot spot.

  • ImageMap implements IPostBackEventHandler to provide server-side event functionality (as described in Chapter 9) when it is used in postback mode. In this mode, ImageMap maps the postback event to the server-side MapClick event, to which a page developer can attach an event handler.

Implementing State Management in a Collection Type ”The HotSpotCollection Example

We'll now look at the implementation of the HotSpotCollection class, which is the type of the HotSpots property of the ImageMap control. HotSpotCollection is a list (it implements IList ) and participates in state management by implementing IStateManager .

The complete code for the HotSpotCollection class appears in the book's sample files; Listing 10-16 shows the code that is relevant for state management. HotSpotCollection contains a private ArrayList field (named _ hotSpots ), which provides the core list functionality of the class. HotSpotCollection exposes an indexer property that accesses the private _ hotSpots field and implements IList methods that are mindful of state management when they add or remove from the underlying ArrayList . For example, the Add and Insert methods of HotSpotCollection allow CircleHotSpot and RectangleHotSpot objects to be added, but not other types that derive from HotSpot . This helps to reduce the size of the view state, as we explain after Listing 10-16. The EditorAttribute applied to the HotSpotCollection class enables the collection editor UI shown in Figure 10-5.

Listing 10-16 Code related to state management in the HotSpotCollection class
 usingSystem; usingSystem.Collections; usingSystem.ComponentModel; usingSystem.Drawing.Design; usingSystem.Web.UI; namespaceMSPress.ServerControls{ [ Editor(typeof(MSPress.ServerControls.Design.HotSpotCollectionEditor), typeof(UITypeEditor)) ] publicsealedclassHotSpotCollection:IList,IStateManager{ privateArrayList_hotSpots; privatebool_isTrackingViewState; privatebool_saveAll; internalHotSpotCollection(){ _hotSpots=newArrayList(); } publicHotSpotthis[intindex]{ get{ return(HotSpot)_hotSpots[index]; } } publicintAdd(HotSpotitem){ if(item==null){ thrownewArgumentNullException("item"); } 
 if(!((itemisCircleHotSpot) (itemisRectangleHotSpot))){ thrownewArgumentException("Itemmustbea"+ "CircleHotSpotoraRectangleHotSpot."); } _hotSpots.Add(item); if(_isTrackingViewState){ ((IStateManager)item).TrackViewState(); item.SetDirty(); } return_hotSpots.Count-1; } publicvoidClear(){ _hotSpots.Clear(); if(_isTrackingViewState){ _saveAll=true; } } publicvoidInsert(intindex,HotSpotitem){ if(item==null){ thrownewArgumentNullException("item"); } if(!((itemisCircleHotSpot) (itemisRectangleHotSpot))){ thrownewArgumentException("Itemmustbea"+ "CircleHotSpotoraRectangleHotSpot."); } _hotSpots.Insert(index,item); if(_isTrackingViewState){ ((IStateManager)item).TrackViewState(); _saveAll=true; } } publicvoidRemoveAt(intindex){ _hotSpots.RemoveAt(index); if(_isTrackingViewState){ _saveAll=true; } } #regionIStateManagerImplementation boolIStateManager.IsTrackingViewState{ get{ return_isTrackingViewState; } } voidIStateManager.LoadViewState(objectsavedState){ if(savedState==null){ return; } if(savedStateisPair){ //Allitemsweresaved. //CreatenewHotSpotscollectionusingviewstate. _saveAll=true; Pairp=(Pair)savedState; ArrayListtypes=(ArrayList)p.First; ArrayListstates=(ArrayList)p.Second; intcount=types.Count; _hotSpots=newArrayList(count); for(inti=0;i<count;i++){ HotSpothotSpot=null; if(((char)types[i]).Equals('c')){ hotSpot=newCircleHotSpot(); } else{ hotSpot=newRectangleHotSpot(); } Add(hotSpot); ((IStateManager)hotSpot).LoadViewState(states[i]); } } else{ //Loadmodifieditems. Triplett=(Triplet)savedState; ArrayListindices=(ArrayList)t.First; ArrayListtypes=(ArrayList)t.Second; ArrayListstates=(ArrayList)t.Third; for(inti=0;i<indices.Count;i++){ intindex=(int)indices[i]; if(index<this.Count){ ((IStateManager) _hotSpots[index]).LoadViewState(states[i]); } else{ HotSpothotSpot=null; if(((char)types[i]).Equals('c')){ hotSpot=newCircleHotSpot(); } else{ hotSpot=newRectangleHotSpot(); } Add(hotSpot); ((IStateManager) hotSpot).LoadViewState(states[i]); } } } } voidIStateManager.TrackViewState(){ _isTrackingViewState=true; foreach(HotSpothotSpotin_hotSpots){ ((IStateManager)hotSpot).TrackViewState(); } } objectIStateManager.SaveViewState(){ if(_saveAll==true){ //Saveallitems. ArrayListtypes=newArrayList(Count); ArrayListstates=newArrayList(Count); for(inti=0;i<Count;i++){ HotSpothotSpot=(HotSpot)_hotSpots[i]; hotSpot.SetDirty(); if(hotSpotisCircleHotSpot){ types.Add('c'); } else{ types.Add('r'); } states.Add(((IStateManager)hotSpot).SaveViewState()); } if(types.Count>0){ returnnewPair(types,states); } else{ returnnull; } } else{ //Saveonlythedirtyitems. ArrayListindices=newArrayList(); ArrayListtypes=newArrayList(); ArrayListstates=newArrayList(); for(inti=0;i<Count;i++){ HotSpothotSpot=(HotSpot)_hotSpots[i]; objectstate= ((IStateManager)hotSpot).SaveViewState(); if(state!=null){ states.Add(state); indices.Add(i); if(hotSpotisCircleHotSpot){ types.Add('c'); } else{ types.Add('r'); } } } if(indices.Count>0){ returnnewTriplet(indices,types,states); } returnnull; } } #endregionIStateManagerImplementation } //TheimplementationofICollectionandIEnumerable //andofseveralmethodsofIListisnotshown. } 

Let's examine the methods of HotSpotCollection that add, remove, and insert items and are involved in state management.

The Add method checks whether state tracking is turned on. If state tracking is on, the method turns on tracking for the added item by invoking its TrackViewState method. The CircleHotSpot and RectangleHotSpot items that are added to the collection implement IStateManager . In addition, if tracking is on, the Add method invokes the SetDirty method on the added item, which causes the entire state of the added HotSpot to be persisted in the view state of HotSpotCollection . This is necessary because when an item is added after initialization of the HotSpotCollection , the item's initial state is not present in the collection's initial state.

The Insert and RemoveAt methods perform their main task and then check whether state tracking is turned on. If this tracking is on, the methods set the private Boolean _ saveAll field to true . This tells the SaveViewState method to save all items in the view state, not just the modified items. When items are inserted or removed in a list, the initial order is lost and the list cannot be re-created by using changes alone. In this case, it is necessary to persist the entire list in view state and use it to re-create the list on postback. The Clear method also sets _ saveAll to true . In that case, a null object is saved in view state to re-create an empty list on postback.

Now we'll examine how HotSpotCollection implements IStateManager .

In the TrackViewState method, HotSpotCollection sets its own _ isTrackingViewState field to true and invokes TrackViewState on each HotSpot in its _ hotSpots list.

The SaveViewState implementation has two branches based on the value of _ saveAll field.

If _ saveAll is true , SaveViewState saves the state of all the items in the list. SaveViewState creates two ArrayList s: the first holds type information for the HotSpot s, and the second holds the states of the HotSpot s. As we described earlier, the list must be re-created entirely from the saved state when _ saveAll is true . Therefore, SaveViewState invokes SetDirty on each HotSpot so that the entire state of the HotSpot is persisted in view state. SaveViewState next cycles through the _ hotSpots list and adds the type and the state of each HotSpot to the two ArrayLists it created earlier. SaveViewState obtains the view state for each HotSpot by invoking SaveViewState on that HotSpot object. Finally, SaveViewState returns a Pair object that holds the two ArrayList s. The returned object constitutes the view state of the HotSpotCollection .

If _ saveAll is false , SaveViewState saves only the changed items in the list. It creates three ArrayList s: the first holds indexes of the changed items, the second holds type information for the HotSpot s, and the third holds the states of the HotSpot s. The method cycles through its _ hotSpots list and invokes SaveViewState on each HotSpot . If the returned state is non-null, SaveViewState adds the index of the HotSpot , its type, and its state to the corresponding ArrayList . Finally, SaveViewState returns a Triplet object that holds the three ArrayList s. (A System.Web.UI.Triplet class is merely an ordered set of three objects.) The returned Triplet object constitutes the view state of the HotSpotCollection .

The LoadViewState method performs the inverse logic of the SaveViewState method. LoadViewState has two branches based on the type of the object handed to it.

If the saved state is a Pair , LoadViewState discards the initial state of HotSpotCollection and re-creates the _ hotSpots list by using the saved state. LoadViewState creates a new, empty _ hotSpots list. It then creates each HotSpot based on the saved type information and loads the corresponding saved state into the HotSpot . Next LoadViewState adds the HotSpot to the _ hotSpots list.

If the saved state is a Triplet , LoadViewState restores state for the items that were changed after initialization ”that is, the items whose indexes appear in the first ArrayList in the Triplet . Items whose indexes are greater than the initial Count of the HotSpotCollection did not exist during initialization and must be created entirely from the saved state. In that case, LoadViewState creates a HotSpot by using type information in the second ArrayList , loads state into it from the third ArrayList , and adds it to the _ hotSpots field.

Because view state tracking is on when LoadViewState is invoked by the ImageMap control on its HotSpotCollection , any items in HotSpotCollection that are added or modified in the LoadViewState method are automatically tracked. This causes the items to be repersisted in view state when SaveViewState is subsequently invoked.

Now let's examine why the HotSpotCollection must restrict its items to specific HotSpot s, such as CircleHotSpot and RectangleHotSpot . The SaveViewState method must save in view state information that specifies the type of HotSpot to be created on postback. To keep the view state small, we store this information as a single character (' c ' or ' r '). If HotSpotCollection allowed any type that derived from HotSpot , we would have to save much more information ”in other words, we'd have to save the assembly-qualified name of the type ”so that the type could be re-created by using reflection on postback. This would lead to significant view state overhead.

If you want to implement a more general ImageMap control that also allows the polygonal shapes permitted by the HTML map specification, you can define a new PolygonHotSpot that derives from HotSpot and has a Shape property that is a MapPolygon type that derives from the MapShape class in Listing 10-3. Then you can modify the implementation of HotSpotCollection to allow a PolygonHotSpot type as a member and save information about that type by using a single character, such as 'p'.



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