ListView Implementation


ListView Implementation

The ListView implementation is based on several of the core control-authoring concepts that we described in Part III of the book, "Server Controls ”Nuts and Bolts." The following sections illustrate those concepts by using fragments from the source code for the ListView control and its related classes. The complete implementation is provided in the WebControls directory along with the code samples from the book, which you'll find on its companion Web site.

Note

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


Data-Bound Controls

The ListView control illustrates the basic characteristics and implementation patterns that apply to all data-bound controls, as discussed in Chapter 16.

Listing 20-3 illustrates these core aspects of data-bound controls.

Listing 20-3 Implementing the essentials of a data-bound control
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  privateobject_dataSource;  privateDataKeyCollection_dataKeys;  publicvirtualobjectDataSource{  get{ return_dataSource; } set{ if((value==null)(valueisIListSource) (valueisIEnumerable)){ _dataSource=value; } else{ thrownewArgumentException(); } } }  publicvirtualstringDataMember{  get{...} set{...} } 
  publicvirtualstringDataKeyField{  get{...} set{...} }  publicDataKeyCollectionDataKeys{  get{ if(_dataKeys==null){ _dataKeys=newDataKeyCollection(this.DataKeysArray); } return_dataKeys; } } privateArrayListDataKeysArray{ get{ objecto=ViewState["DataKeys"]; if(o==null){ o=newArrayList(); ViewState["DataKeys"]=o; } return(ArrayList)o; } }  publicoverridevoidDataBind(){  //Data-boundcontrolsimplementcustomdata-bindinglogicby //overridingthismethod. //WestillwanttheDataBindingeventtofire,soany //<%#%>expressionsonthiscontrolgetevaluatedfirst. base.OnDataBinding(EventArgs.Empty); //Nowre-createthenewcontrolhierarchyusingtheassigned //datasource. Controls.Clear(); //Wealsowanttothrowoutanyviewstateforchildrenifit //existsbecausewe'recreatinganewhierarchy.Andthen //starttrackingchangesmadeduringthedata-bindingprocess. ClearChildViewState(); TrackViewState(); CreateControlHierarchy(true); //Marktheflagindicatingchildcontrolshavebeencreated //sothatCreateChildControlsdoesnotgetcalled. ChildControlsCreated=true; }  protectedoverridevoidCreateChildControls(){  //Ifthisgetscalled,wearere-creatingchildren(theitems) //fromviewstate. Controls.Clear(); //Wecancreatetheitemsifwehaveviewstate,sowecheck //forthenumberofitemswecreatedduringapreviousrequest //viaacalltotheDataBindmethod. if(ViewState["Items"]!=null){ CreateControlHierarchy(false); } }  protectedvirtualvoidCreateControlHierarchy(booluseDataSource){  IEnumerabledataSource=null; intitemCount=0; _items=null; ArrayListdataKeysArray=DataKeysArray; stringdataKeyField=null; if(useDataSource){ dataSource=GetDataSource(); dataKeysArray.Clear(); dataKeyField=DataKeyField; } else{ dataSource=newobject[(int)ViewState["Items"]]; } if(dataSource!=null){ TableouterTable=newTable(); Controls.Add(outerTable); ListViewItemheaderItem=null; ListViewItemfooterItem=null; if(_headerTemplate!=null){ TableRowheaderRow=newTableRow(); outerTable.Rows.Add(headerRow); headerItem= CreateListViewItem(headerRow,-1, ListViewItemType.Header,null,useDataSource); } TableRowbodyRow=newTableRow(); outerTable.Rows.Add(bodyRow); TableCellbodyCell=newTableCell(); bodyRow.Cells.Add(bodyCell); ListViewPanelviewPanel=newListViewPanel(); bodyCell.Controls.Add(viewPanel); ListViewTableinnerTable=CreateListViewTable(); viewPanel.Controls.Add(innerTable); TableRowitemsRow=newTableRow(); innerTable.Rows.Add(itemsRow); inteditIndex=EditIndex; intselectedIndex=SelectedIndex; intitemIndex=0; foreach(objectdataItemindataSource){ ListViewItemTypeitemType=ListViewItemType.Item; if(itemIndex==editIndex){ itemType=ListViewItemType.EditItem; } if(itemIndex==selectedIndex){ itemType=ListViewItemType.SelectedItem; } CreateListViewItem(itemsRow,itemIndex,itemType, dataItem,useDataSource); itemIndex++; itemCount++; if(useDataSource&&(dataKeyField.Length!=0)){ dataKeysArray.Add(DataBinder.GetPropertyValue(dataItem, dataKeyField)); } } if(_footerTemplate!=null){ TableRowfooterRow=newTableRow(); outerTable.Rows.Add(footerRow); CreateListViewItem(footerRow,-1, ListViewItemType.Footer,null,useDataSource); } _items=CreateListViewItemCollection(itemsRow.Cells, headerItem,footerItem); } if(useDataSource){ ViewState["Items"]=itemCount; } } protectedvirtualListViewItemCreateListViewItem(intitemIndex, ListViewItemTypeitemType){ returnnewListViewItem(itemIndex,itemType); }  privateListViewItemCreateListViewItem(TableRowrowContainer, intitemIndex,ListViewItemTypeitemType,objectdataItem, booldataBind){  ListViewItemitem=CreateListViewItem(itemIndex,itemType); ListViewItemEventArgse=newListViewItemEventArgs(item); ITemplatetemplate=GetTemplateForItem(item); if(template!=null){ template.InstantiateIn(item); } OnItemCreated(e); rowContainer.Cells.Add(item); if(dataBind){ item.DataItem=dataItem; item.DataBind(); OnItemDataBound(e); } returnitem; } protectedvirtualListViewItemCollection CreateListViewItemCollection(TableCellCollectioncells, ListViewItemheaderItem,ListViewItemfooterItem){ returnnewListViewItemCollection(cells,headerItem, footerItem); } protectedvirtualListViewTableCreateListViewTable(){ returnnewListViewTable(); }  protectedvirtualIEnumerableGetDataSource(){  if(_dataSource==null){ returnnull; } IEnumerableresolvedDataSource=_dataSourceasIEnumerable; if(resolvedDataSource!=null){ returnresolvedDataSource; } IListSourcelistSource=_dataSourceasIListSource; if(listSource!=null){ IListmemberList=listSource.GetList(); if(listSource.ContainsListCollection==false){ return(IEnumerable)memberList; } ITypedListtypedMemberList=memberListasITypedList; if(typedMemberList!=null){ PropertyDescriptorCollectionpropDescs= typedMemberList.GetItemProperties(newPropertyDescriptor[0]); PropertyDescriptormemberProperty=null; if((propDescs!=null)&&(propDescs.Count!=0)){ stringdataMember=DataMember; if(dataMember.Length==0){ memberProperty=propDescs[0]; } else{ memberProperty= propDescs.Find(dataMember,true); } if(memberProperty!=null){ objectlistRow=memberList[0]; objectlist=memberProperty.GetValue(listRow); if(listisIEnumerable){ return(IEnumerable)list; } } thrownewException("Alistcorrespondingto " +  "theselectedDataMemberwasnotfound."); } thrownewException("Theselecteddatasourcedid " +  "notcontainanydatamemberstobindto."); } } returnnull; }  protectedvirtualvoidOnItemCreated(ListViewItemEventArgse){  ListViewItemEventHandlerhandler= (ListViewItemEventHandler)Events[EventItemCreated]; if(handler!=null){ handler(this,null); } }  protectedvirtualvoidOnItemDataBound(ListViewItemEventArgse){  ListViewItemEventHandlerhandler= (ListViewItemEventHandler)Events[EventItemDataBound]; if(handler!=null){ handler(this,null); } } } 

The primary characteristic of a data-bound control is its DataSource property. ListView exposes a DataSource property of type Object . A control should allow the page developer to use a variety of objects as valid data sources when appropriate. ListView accepts objects that implement either the IEnumerable or the IListSource interfaces as valid data sources. Objects that implement these interfaces include arbitrary arrays, all collections such as ArrayList , and other data-centric objects, such as DataReader , DataSet , and DataView . Note that the assigned data source is held in a member variable, instead of in the ViewState dictionary. This is because a data source is a reference to another object that is valid only during a single request and should not be serialized into a textual format.

A data-bound control enumerates the data contained in its associated data source. Therefore, the most basic interface that an object must implement to be used as a data source is IEnumerable . When the object implements IListSource instead, which has the semantics of a collection of lists of data, ListView uses the value of its DataMember property to identify the specific list to use as the actual data source object that is enumerated. For example, the DataSet class implements the IListSource interface and contains a collection of DataView objects, each representing a specific DataTable contained in the DataSet . ListView encapsulates the logic of extracting the appropriate list of data to enumerate from the assigned IListSource object in its GetDataSource helper method. The GetDataSource method was described in detail in Chapter 16, along with the implementation of the DataBoundTable control.

The other characteristic shared by all data-bound controls is the implementation of data-binding logic in an override of the DataBind method. ListView first calls the OnDataBinding method to raise the DataBinding event. This event triggers the evaluation of any data-binding expressions that the user might have specified for properties of the ListView control. Once these expressions have been evaluated, the ListView control proceeds to enumerate the data contained within its assigned data source. The DataBind method is the only method in which a data-bound control should access its data source. The ListView control clears its current controls collection and any view state associated with its child controls because it creates a new control hierarchy in its data-binding implementation. ListView encapsulates the logic of creating the control hierarchy in a method named CreateControlHierarchy , which is described next . Once ListView has created its control hierarchy, it sets the ChildControlsCreated property to true so that its CreateChildControls method will not be called.

ListView also calls the same CreateControlHierarchy method from its override of the CreateChildControls method. CreateChildControls is called when the ListView control is required to create its control hierarchy because the page developer has not explicitly invoked the data-binding process via a call to the DataBind method. In this scenario, the ListView control is required to re-create the same hierarchy from information that it stored in view state during the data-binding process.

The CreateControlHierarchy method encapsulates the logic of creating the control hierarchy for both scenarios ”when the control hierarchy is created as part of the data-binding process and when the control hierarchy is re-created from view state. This encapsulation forces a single implementation to create the control hierarchy and ensures that the resulting hierarchy is the same in both scenarios. This process is important because view state for controls gets reloaded into controls based on their relative positions within the control tree. In this method, the control enumerates the data source, creates the appropriate controls to represent each enumerated object, and adds those controls to the control hierarchy. The resulting control hierarchy is recursively data-bound if the ListView control is itself in the process of being data bound. To use the same code path and enumeration logic, the implementation of CreateControlHierarchy creates an array of null values as a dummy data source and enumerates that array when the control hierarchy is being re-created from view state. In this case, the actual values are restored from view state. Finally, when CreateControlHierarchy is called during the data-binding process, it stores the number of objects enumerated in the control's view state. This number is retrieved to determine the number of values to create in the dummy data source.

In ListView , the implementation of CreateControlHierarchy creates a Table as a child control, which contains a header, footer, and table body. The table body contains a Panel , which in turn contains a nested table. This nested table contains a number of ListViewItem objects that are derived from the TableCell class. Each ListViewItem corresponds to a single enumerated object in the data source. Because it contains these child controls, the ListView control is a composite control. In addition, ListView contains child controls that might have associated postback data or might require events routed to them. Therefore, ListView implements the INamingContainer interface. Composite controls were described in Chapter 12, "Composite Controls."

The ListView control raises the ItemCreated and ItemDataBound events to allow a page developer to programmatically customize the creation and data binding of controls used to render the contents of the data source. The ItemCreated event is raised when a ListViewItem is created and has been initialized to its default state. The page developer can handle this event to customize the layout and controls contained within the ListViewItem before that control is added to the control hierarchy. The ItemDataBound event is raised when a ListView ­Item has been data-bound. The page developer can handle this event to perform additional data-binding logic or customize the controls contained within the ListViewItem based on the data they are bound to. This event does not occur when the ListView control re-creates its control hierarchy from its saved view state.

Templates

The ListView control offers various template properties (such as ItemTemplate , HeaderTemplate , and FooterTemplate ) that enable the page developer to customize the layout and content of the rendered UI. Templates were discussed in Chapter 12.

Listing 20-4 illustrates the implementation of template properties in ­ListView .

Listing 20-4 Implementing a templated control
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  privateITemplate_itemTemplate;  publicITemplateEditItemTemplate{ get{...} set{...} } publicITemplateFooterTemplate{ get{...} set{...} } publicITemplateHeaderTemplate{ get{...} set{...} } [ TemplateContainer(typeof(ListViewItem)) ]  publicITemplateItemTemplate{  get{ return_itemTemplate; } set{ _itemTemplate=value; } }  privateListViewItemCreateListViewItem(TableRowrowContainer, intitemIndex,ListViewItemTypeitemType,objectdataItem, booldataBind){  ListViewItemitem=CreateListViewItem(itemIndex,itemType); ListViewItemEventArgse=newListViewItemEventArgs(item); 
 ITemplatetemplate=GetTemplateForItem(item); if(template!=null){ template.InstantiateIn(item); } OnItemCreated(e); rowContainer.Cells.Add(item); if(dataBind){ item.DataItem=dataItem; item.DataBind(); OnItemDataBound(e); } returnitem; }  protectedvirtualITemplateGetTemplateForItem(ListViewItemitem){  ITemplatetemplate=null; switch(item.ItemType){ caseListViewItemType.Header: template=_headerTemplate; break; caseListViewItemType.Footer: template=_footerTemplate; break; default: template=_itemTemplate; if((item.ItemType&ListViewItemType.EditItem)!=0){ if(_editItemTemplate!=null){ template=_editItemTemplate; } } break; } returntemplate; } } 

The ListView control creates an instance of the ListViewItem template container for each object it enumerates in the data source. Listing 20-5 contains the implementation of the ListViewItem class.

Listing 20-5 The ListViewItem implementation, which contains the instantiated template
 publicclassListViewItem:TableCell,INamingContainer{ privateint_itemIndex; privateobject_dataItem; privateListViewItemType_itemType; publicListViewItem(intitemIndex,ListViewItemTypeitemType){ _itemIndex=itemIndex; _itemType=itemType; } publicvirtualobjectDataItem{ get{ return_dataItem; } set{ _dataItem=value; } } publicintItemIndex{ get{ return_itemIndex; } } publicvirtualListViewItemTypeItemType{ get{ return_itemType; } }  } 

The ListView control contains several template properties (such as ItemTemplate ) that are typed as System.Web.UI.ITemplate . The implementation of a template property is straightforward. The property getter and setter simply provide access to a template field. The ASP.NET page parser does the work of parsing template content and assigning the control an ITemplate instance as its property value, as we described in Chapter 12.

The ListView control uses its templates by calling the InstantiateIn method of ITemplate as it creates and adds ListViewItem controls to its control hierarchy in the CreateListViewItem method. Each ListViewItem is a naming container. This preserves the uniqueness of IDs across individual ListViewItem instances, even though the same template can be instantiated more than once. When the ListView control creates its control hierarchy during the data-binding process, it calls the DataBind method of each ListViewItem it creates, which recursively calls the DataBind method on each control created from the template. This ensures that any data-binding expressions within the template are evaluated as the ListView control enumerates data in its data source.

Data-binding expressions that contain the text "Container.DataItem" are common. In this context, Container represents the naming container of the template's contents. In the ListView control, each ListViewItem is the naming container for the contents of the template instantiated within it. The page parser requires knowledge of the type of the container so that it can correctly declare a variable named Container in the code it generates and so that the compiler can successfully resolve the expression Container.DataItem . To retrieve this information, the page parser examines a template property for the TemplateContainerAttribute metadata attribute. You can use this attribute to specify the actual type of the naming container for a specific template. The ListView control uses this metadata attribute to indicate that the container for its templates is a ListViewItem .

Styles and State Management

The ListView control offers various style properties (such as ItemStyle and SelectedItemStyle ) that enable the page developer to customize the visual characteristics of the rendered UI. We discussed styles and state management related to styles in Chapter 11, "Styles in Controls." We discussed how to utilize typed styles for child controls in the StyledCompositeLogin example in Chapter 12.

Listing 20-6 illustrates the implementation of style properties in ListView .

Listing 20-6 Implementing style properties and managing their view state
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  privateTableItemStyle_itemStyle; publicTableItemStyleItemStyle{  get{ if(_itemStyle==null){ _itemStyle=newTableItemStyle(); if(IsTrackingViewState){ ((IStateManager)_itemStyle).TrackViewState(); } } return_itemStyle; } } 
 publicTableItemStyleEditItemStyle{ get{...} } publicTableItemStyleFooterStyle{ get{...} }  publicvirtualGridLinesGridLines{  get{ if(ControlStyleCreated==false){ returnGridLines.None; } return((TableStyle)ControlStyle).GridLines; } set{ ((TableStyle)ControlStyle).GridLines=value; } } publicTableItemStyleHeaderStyle{ get{...} } publicTableItemStyleSelectedItemStyle{ get{...} }  protectedoverrideStyleCreateControlStyle(){  //BecausetheListViewrendersanHTMLtable,a //aninstanceofaTableStyleisusedasthecontrolstyle. TableStylestyle=newTableStyle(ViewState); //Thisisalsotherightspottoinitializethestyle. style.CellSpacing=0; returnstyle; }  protectedoverridevoidLoadViewState(objectsavedState){  objectbaseState=null; object[]myState=null; if(savedState!=null){ myState=(object[])savedState; Debug.Assert(myState.Length==6); baseState=myState[0]; } //Alwayscallthebaseclass,evenifthestateisnull,so //thatthebaseclassgetsachancetofullyimplementits //LoadViewStatefunctionality. base.LoadViewState(baseState); if(myState==null){ return; } //Forperformancereasons,thestylesarecreatedonlyif //stateexistsforthem. if(myState[1]!=null) ((IStateManager)ItemStyle).LoadViewState(myState[1]);  }  protectedoverrideobjectSaveViewState(){  object[]myState=newobject[6]; //Again,thestylesaresavedonlyiftheyhave //beencreated. myState[0]=base.SaveViewState(); myState[1]=(_itemStyle!=null)? ((IStateManager)_itemStyle).SaveViewState():null;  //Wedon'tcheckforallnullsbecausethecontrolis //almostcertaintohavesomeviewstate,becauselike //mostdata-boundcontrols,itsavesinformationto //re-createitselfwithoutalivedatasourceon //round-trips. returnmyState; }  protectedoverridevoidTrackViewState(){  base.TrackViewState(); //Again,thetrackingispropagatedonlytothose //stylesthathavebeencreated.Newstylescreated //thereafterwillbemarkedastrackingviewstatewhen //theyaredemand-created. if(_itemStyle!=null) ((IStateManager)_itemStyle).TrackViewState();  } } 

ListView implements each of its style properties, such as ItemStyle as read-only properties. In the property getter, ListView creates an instance of TableItemStyle on demand. It is important that styles are created only when they are required because they can impact performance by affecting the size of view state and by increasing the complexity of the rendering process. Style properties are always implemented as read-only properties. This allows the control to assume full responsibility for the style's state management.

Styles contain the ability to manage their properties in view state. However, it is the responsibility of the control that is using a style to include the style instance in its own implementation of state management. This is described briefly here. Chapter 12 gives a more detailed explanation.

ListView overrides the SaveViewState method to return an object that includes the view state corresponding to each of its style properties. The control creates an object array that contains the view state of its base class and the view state of each of its style properties. ListView 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 into an object array. The LoadViewState override then calls the corresponding method of its base class. The implementation then loads any non-null view state into the corresponding style properties. Finally, ListView allows its style properties to manage their properties in view state by overriding the TrackViewState method and calling TrackViewState on each non-null style instance. 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. ListView calls the same method to mark the end of the Initialize phase of any styles that have been created. The style property getter also calls TrackViewState on any style created subsequently.

The ListView control overrides its CreateControlStyle method to create a TableStyle . ListView applies this control style to the Table control it contains. The control style is the control's primary style and is used to implement a control's top-level style properties, such as ForeColor , BackColor , and Font . TableStyle contains additional table-specific style properties, such as GridLines and CellSpacing . These properties are exposed as top-level style properties by ListView and are implemented by delegating to the corresponding properties of the underlying control style instance.

Rendering

The ListView control renders a multicolumn list of ListViewItem controls in which each ListViewItem represents a single object in the data source that the ListView is associated with. We described the rendering process and related rendering methods in Chapter 8, "Rendering."

Listing 20-7 illustrates how ListView overrides the appropriate rendering methods to generate its HTML representation. It defines the PrepareControlHierarchyForRendering helper method, which performs the task of applying styles to child controls before they are rendered.

Listing 20-7 Customizing the rendering process
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  protectedvirtualvoidPrepareControlHierarchyForRendering(){  ControlCollectioncontrols=Controls; if(controls.Count!=1){ return; } TableouterTable=(Table)controls[0]; outerTable.CopyBaseAttributes(this); if(ControlStyleCreated){ outerTable.ApplyStyle(ControlStyle); } else{ //Becausewedidn'tcreateaControlStyleyet,the //settingsforthedefaultstyleofthecontrolneedto //beappliedtothechildtablecontroldirectly. outerTable.CellSpacing=0; } TableRowCollectionrows=outerTable.Rows; TableCellbodyCell=null; if(_headerTemplate!=null){ TableRowheaderRow=rows[0]; if(ShowHeader){ if(_headerStyle!=null){ headerRow.Cells[0].MergeStyle(_headerStyle); } } else{ headerRow.Visible=false; } 
 bodyCell=rows[1].Cells[0]; } if(_footerTemplate!=null){ TableRowfooterRow=rows[rows.Count-1]; if(ShowFooter){ if(_footerStyle!=null){ footerRow.Cells[0].MergeStyle(_footerStyle); } } else{ footerRow.Visible=false; } } if(bodyCell==null){ bodyCell=rows[0].Cells[0]; } ListViewPanelviewPanel=(ListViewPanel)bodyCell.Controls[0]; if(_viewStyle!=null){ viewPanel.ApplyStyle(_viewStyle); if(ShowScrollBars){ viewPanel.Style["overflow"]= "scroll"; viewPanel.Style["overflow-x"]= "auto"; viewPanel.Style["overflow-y"]= "auto"; } } ListViewTablebodyTable=(ListViewTable)viewPanel.Controls[0]; bodyTable.Columns=Columns; foreach(ListViewItemitemin_items){ TableItemStylestyle=_itemStyle; TableItemStylecompositeStyle=null; ListViewItemTypeitemType=item.ItemType; if(((itemType&ListViewItemType.EditItem)!=0)&& (_editItemStyle!=null)){ if(style!=null){ compositeStyle=newTableItemStyle(); compositeStyle.CopyFrom(style); compositeStyle.CopyFrom(_editItemStyle); } else{ style=_editItemStyle; } } if(((itemType&ListViewItemType.SelectedItem)!=0)&& (_selectedItemStyle!=null)){ if(compositeStyle!=null){ compositeStyle.CopyFrom(_selectedItemStyle); } elseif(style!=null){ compositeStyle=newTableItemStyle(); compositeStyle.CopyFrom(style); compositeStyle.CopyFrom(_selectedItemStyle); } else{ style=_selectedItemStyle; } } if(compositeStyle!=null){ item.MergeStyle(compositeStyle); } elseif(style!=null){ item.MergeStyle(style); } if(_renderClickSelectScript){ if((itemType&ListViewItemType.SelectedItem)==0){ item.Attributes["onclick"]= Page.GetPostBackEventReference(this,  "S" +item.ItemIndex); item.Style["cursor"]= "hand"; } } } }  protectedoverridevoidRender(HtmlTextWriterwriter){  //Stylesareappliedaslateastherendertime. //a)UsercanchangestylesaftercallingDataBind. //b)Changesmadetoitemsduringstyleapplicationdo //notcontributetotheviewstate.Thiscontrol //managesthestateforstyles,sohavingitems //manageitaswellwouldberedundant. PrepareControlHierarchyForRendering(); //Wedon'trenderouttagscorrespondingtoListView //itself.Weneedtorenderitscontentsonly. //Therefore,insteadofcallingbase.Render,wecall //RenderContents. RenderContents(writer); } } 

Each server control typically renders a single top-level tag. ListView renders an HTML <table> as its top-level tag. The ListView control itself does not generate any tag of its own. ListView generates the entire <table> tag by rendering the Table control that it contains in its Controls collection. Therefore, the ListView control overrides its Render method to skip the rendering logic in its base class and instead calls RenderContents , which renders the contents of its Controls collection.

Before calling RenderContents , the Render method override in ListView calls the PrepareControlHierarchyForRendering method. This method is responsible for applying style attributes to the contained items, setting the visibility of the items, and so on. These operations are performed during the Render phase for two primary reasons. First, delaying the application process allows the page developer to change style properties and toggle the visibility of various parts of the ListView control at any point in the page life cycle before the Render phase. Second, the application of styles does not result in any additional view state when done during the Render phase. This is because any state that needs to be managed is collected during the Save View State phase, which precedes the Render phase.

Listing 20-8 contains the implementation of the ListViewTable helper class used by the ListView control. ListViewTable holds a collection of ListViewItem controls as the cells of its first row and renders them into a multicolumn list. The ListViewTable instance is created by the CreateControlHierarchy method of ListView .

Listing 20-8 Implementation of ListViewTable , which renders its cells into a multicolumn list
 publicclassListViewTable:Table{ privateint_columns; publicListViewTable(){ _columns=1; CellSpacing=4; CellPadding=0; GridLines=GridLines.None; BorderWidth=0; 
 Width=Unit.Percentage(100); Height=Unit.Percentage(100); }  publicintColumns{  get{ return_columns; } set{ if(value<1){ thrownewArgumentOutOfRangeException("value"); } _columns=value; } }  protectedoverridevoidRenderContents(HtmlTextWriterwriter){  if(Rows.Count!=1){ return; } TableCellCollectioncells=Rows[0].Cells; intcellsRendered=0; boolendTagRequired=false; foreach(TableCellcellincells){ if(cellsRendered==0){ writer.RenderBeginTag(HtmlTextWriterTag.Tr); endTagRequired=true; } if(cell.Visible){ cell.RenderControl(writer); cellsRendered++; } if(cellsRendered==_columns){ writer.RenderEndTag(); endTagRequired=false; cellsRendered=0; } } if(endTagRequired){ writer.RenderEndTag(); } } } 

The ListViewTable class derives from the Table control. Like all other tables, ListViewTable can contain rows and its rows can contain cells. The ListView control creates instances of ListViewItem classes and adds them to the Cells collection of the first row of ListViewTable . ListViewTable overrides the default rendering of a Table by overriding the RenderContents method. Instead of rendering its rows, ListViewTable renders ListViewItem s directly by rendering the cells contained in its first row. In addition, ListViewTable renders markup to create <tr> tags between the cells so that the result is a rendering of cells distributed across one or more columns. The number of columns is exposed as a property on the ListViewTable class, which is initialized by the ListView control.

Events

A data-bound control typically exposes its own set of events based on events raised by the controls in its control hierarchy. This provides a more intuitive programming model because it does not require the page developer to wire up event handler delegates to individual controls created by ListView . The ListView control raises the SelectedIndexChanged and the ItemCommand events, which allow the page developer to write server-side code to handle the user's interactions with the control's rendering in a Web browser. Raising server-side events for user actions was discussed in Chapter 9, "Control Life Cycle, Events, and Postback."

The event-raising functionality of ListView is shown in Listing 20-9.

Listing 20-9 Handling and raising client events and bubbled server events
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{ privatestaticreadonlyobjectEventSelectedIndexChanged= newobject(); privatestaticreadonlyobjectEventItemCommand=newobject(); publicconststringSelectCommandName= "Select"; publiceventListViewCommandEventHandlerItemCommand{ add{ Events.AddHandler(EventItemCommand,value); } remove{ Events.RemoveHandler(EventItemCommand,value); } } [ Category("Action"), Description("Raisedwhentheselectionchanges") ] 
 publiceventEventHandlerSelectedIndexChanged{ add{ Events.AddHandler(EventSelectedIndexChanged,value); } remove{ Events.RemoveHandler(EventSelectedIndexChanged,value); } } protectedoverrideboolOnBubbleEvent(objectsender,EventArgse){ ListViewCommandEventArgslce=easListViewCommandEventArgs; if(lce!=null){ OnItemCommand(lce); if(lce.CommandType==ListViewCommandType.Select){ intoldSelectedIndex=SelectedIndex; if(oldSelectedIndex!=lce.Item.ItemIndex){ SelectedIndex=lce.Item.ItemIndex; OnSelectedIndexChanged(EventArgs.Empty); } } returntrue; } returnfalse; } protectedvirtualvoidOnItemCommand(ListViewCommandEventArgse){ ListViewCommandEventHandlerhandler= (ListViewCommandEventHandler)Events[EventItemCommand]; if(handler!=null){ handler(this,null); } } protectedvirtualvoidOnSelectedIndexChanged(EventArgse){ EventHandlerhandler= (EventHandler)Events[EventSelectedIndexChanged]; if(handler!=null){ handler(this,null); } } protectedvirtualvoidPrepareControlHierarchyForRendering(){  foreach(ListViewItemitemin_items){  if(_renderClickSelectScript){ if((itemType&ListViewItemType.SelectedItem)==0){ item.Attributes["onclick"]= Page.GetPostBackEventReference(this,  "S" +item.ItemIndex); item.Style["cursor"]= "hand"; } } } } #regionImplementationofIPostBackEventHandler voidIPostBackEventHandler.RaisePostBackEvent(stringeventArgument){ if((eventArgument.Length>1)&&(eventArgument[0]=='S')){ SelectedIndex=Int32.Parse(eventArgument.Substring(1)); OnSelectedIndexChanged(EventArgs.Empty); } } #endregion } 

ListViewItem derives from TableCell and contains an instantiated template within the ListView control hierarchy. ListViewItem participates in the event-bubbling mechanism, as shown in Listing 20-10.

Listing 20-10 Event bubbling in the ListViewItem class
 publicclassListViewItem:TableCell,INamingContainer{  protectedoverrideboolOnBubbleEvent(objectsender,EventArgse){ CommandEventArgsce=easCommandEventArgs; if(ce!=null){ ListViewCommandEventArgslce= newListViewCommandEventArgs(this,sender,ce); RaiseBubbleEvent(this,lce); returntrue; } returnfalse; } } 

ListViewItem handles a bubbled Command event and in turn bubbles a ListViewCommand event. Listing 20-11 shows the implementation of the ListViewCommandEventArgs class and the associated ListViewCommand ­EventHandler delegate.

Listing 20-11 Implementation of ListViewCommandEventArgs and definition of ListViewCommandEventHandler
 publicclassListViewCommandEventArgs:CommandEventArgs{ privateListViewItem_item; privateobject_commandSource; privateListViewCommandType_commandType; publicListViewCommandEventArgs(ListViewItemitem, objectcommandSource,CommandEventArgsoriginalArgs): base(originalArgs){ _item=item; _commandSource=commandSource; stringcmdName=originalArgs.CommandName; if(String.Compare(cmdName,ListView.SelectCommandName,true, CultureInfo.InvariantCulture)==0){ _commandType=ListViewCommandType.Select; } elseif(String.Compare(cmdName,ListView.EditCommandName, true,CultureInfo.InvariantCulture)==0){ _commandType=ListViewCommandType.Edit; } elseif(String.Compare(cmdName,ListView.UpdateCommandName, true,CultureInfo.InvariantCulture)==0){ _commandType=ListViewCommandType.Update; } elseif(String.Compare(cmdName,ListView.CancelEditCommandName, true,CultureInfo.InvariantCulture)==0){ _commandType=ListViewCommandType.CancelEdit; } elseif(String.Compare(cmdName,ListView.DeleteCommandName, true,CultureInfo.InvariantCulture)==0){ _commandType=ListViewCommandType.Delete; } else{ _commandType=ListViewCommandType.Custom; } } publicobjectCommandSource{ get{ return_commandSource; } } publicListViewCommandTypeCommandType{ 
 get{ return_commandType; } } publicListViewItemItem{ get{ return_item; } } } publicdelegatevoidListViewCommandEventHandler(objectsender, ListViewCommandEventArgse); 

The ListView control defines two events that signal user action, ItemCommand and SelectedIndexChanged . As per the standard event implementation described in Chapter 3 , "Component Programming Overview," the ListView control contains two public event properties with add and remove accessors as well as the associated protected virtual methods that raise the event by invoking any event-handler delegates that have been wired up to the event.

The ListView control raises the ItemCommand event when any control (such as a Button or LinkButton ) within the ListView control hierarchy raises the Command event. A template that contains a Button control results in multiple instances of the Button within the control hierarchy. The ItemCommand event allows the page developer to handle an event on a single control rather than handling events on each Button instance. The ListView and ListViewItem classes work together by using event bubbling to implement the event. Event bubbling was described in Chapter 12. Button controls bubble their Command event up their parent hierarchy. The ListViewItem control overrides OnBubbleEvent and handles Command events. The control stops the bubbling of the original event and instead bubbles an ItemCommand event. The ItemCommand event is associated with an instance of ListViewCommandEventArgs and contains a reference to the ListViewItem that contained the Button that the user clicked. This reference adds context to the original Command event, which allows the page developer to take the appropriate action based on the ListViewItem associated with the event. The ListView control also overrides OnBubbleEvent to handle bubbled ItemCommand events, and it invokes any event-handler delegates that have been wired up to this event.

The ListView control also raises the SelectedIndexChanged event. This event occurs in one of two ways. If a page developer places a Button with its CommandName property set to "Select," the ListView control raises the Selected ­IndexChanged event from its override of the OnBubbleEvent method. The second way this event can occur is by the script generated by the ListView control that allows the user to select a ListViewItem by clicking anywhere on an item's rendering in the Web browser. The click causes the page to be submitted back to the server. The ListView control implements the IPostBackEventHandler interface to map client-side events into equivalent server-side events, as described in Chapter 9. In its implementation of the RaisePostBackEvent method, the ListView control selects the appropriate ListViewItem , as identified by the argument passed into the method, and then raises the SelectedIndexChanged event.

Client Script Functionality

The ListView control uses JavaScript in the Web browser client to enable a more intuitive selection behavior. The enhanced selection model provided by the control allows the user to click anywhere on the region representing an item to select it. A page developer can also place a Select button within the ItemTemplate of the ListView to enable selection in Web browsers that do not support the required client-side functionality. We described the basics of using script-based functionality in Chapter 13, "Client-Side Behavior."

Listing 20-12 illustrates various parts of the ListView implementation that are responsible for creating this client-side experience.

Listing 20-12 Implementing client-side behavior to enable an enhanced selection model
 publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  privatebool_renderClickSelectScript; publicboolEnableClickSelect{  get{ objectb=ViewState["EnableClickSelect"]; return(b==null)?false:true; } set{ ViewState["EnableClickSelect"]=value; } }  privatevoidDetermineRenderClickSelectScript(){  //Determinewhethertorenderclientscript-based //functionality. _renderClickSelectScript=false; 
 if((Page!=null)&&(Page.Request!=null)){ //Thepagedevelopercandecidetoturnoffthefeature //completely. if(EnableClickSelect){ //Thenextsetofchecksinvolvelookingatthe //capabilitiesofthebrowsermakingtherequest. HttpBrowserCapabilitiesbrowserCaps= Page.Request.Browser; boolhasEcmaScript= (browserCaps.EcmaScriptVersion.CompareTo(newVersion(1,2))>=0); boolhasDOM=(browserCaps.MSDomVersion.Major>=4); _renderClickSelectScript=hasEcmaScript&&hasDOM; } } }  protectedoverridevoidOnPreRender(EventArgse){  base.OnPreRender(e); DetermineRenderClickSelectScript(); if(_renderClickSelectScript){ //Page.GetPostBackEventReferenceautomatically //registersthepage'spostbackscript.However,if //thisisdoneduringRender,thescriptwillbe //renderedattheendofthepage.Wewantthe //scriptupatthestart,sothebrowserhasalready //seenthescriptwhileit'sstillloadingthe //restofthepage.Thus,clickingitemsworks //whilethepageisloadingaswell. stringdummyValue= Page.GetPostBackEventReference(this,String.Empty); } }  protectedvirtualvoidPrepareControlHierarchyForRendering(){   foreach(ListViewItemitemin_items){  if(_renderClickSelectScript){ if((itemType&ListViewItemType.SelectedItem)==0){ item.Attributes["onclick"]= Page.GetPostBackEventReference(this,  "S" +item.ItemIndex); item.Style["cursor"]= "hand"; } } } } } 

ListView 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-side behavior. The control encapsulates the logic of determining the level of its client-side behavior into a method named Determine ­RenderClickSelectScript . By default, this method inspects the properties of an HttpBrowserCapabilities object, which it retrieves by using the Browser property of the containing page's Request object. This HttpBrowserCapabilities object includes information about the capabilities of the Web browser making the current request. In particular, the implementation checks for the availability of JavaScript 1.2 and later and the DHTML Document Object Model. In addition, this method first checks the value of the control's EnableClickSelect property, which allows page developers to turn off the enhanced selection behavior and the automatic detection logic. This property defaults to true , which enables the automatic detection logic based on the Web browser's capabilities.

To implement its client-side behavior, the ListView control renders Java ­Script to handle the onclick DHTML event on each <td> tag corresponding to a ListViewItem . The control retrieves the script required to programmatically initiate a postback by using the GetPostBackEventReference method of its containing page. This method accepts an event target and an event argument. The control is passed in as the event target, and the index of the ListViewItem is passed in as the event argument. The script is added as the value of the onclick attribute to each ListViewItem . As a result, in the Render phase of the page, each ListViewItem renders itself and its onclick attribute. Note that the control adds these attributes during the Render phase, which follows the Save View State phase ”according to the control life cycle described in Chapter 9. Thus, the changes made to the Attributes collection do not result in additional view state.

Design-Time Attributes

The ListView implementation uses various design-time attributes to specify metadata for its type, properties, and events and to enhance its design-time experience on a visual design surface. We discussed some of these designer attributes in Chapter 15, "Design-Time Functionality," and describe them in Appendix A, "Metadata Attributes."

Listing 20-13 provides a representative sampling of design-time attributes used in the ListView implementation.

Listing 20-13 Various design-time metadata attributes on the class and on its properties and events
  [ DefaultEvent("SelectedIndexChanged"), DefaultProperty("DataSource"), Designer(typeof(MSPress.WebControls.Design.ListViewDesigner), typeof(IDesigner)) ]  publicclassListView:WebControl,INamingContainer, IPostBackEventHandler{  [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ]  publicDataKeyCollectionDataKeys{ get{...} }  [ Bindable(true), Category("Data"), DefaultValue(null), Description("Thedatasourcecontainingdatatoberendered"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) ]  publicvirtualobjectDataSource{ get{...} set{...} }  [ Category("Behavior"), DefaultValue(false), Description("Whethertoenabletheclick-selectbehavior") ]  publicboolEnableClickSelect{ 
 get{...} set{...} }  [ Category("Style"), Description("Thestyleappliedtoallitems"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ]  publicTableItemStyleItemStyle{ get{...} }  [ Browsable(false), DefaultValue(null), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(ListViewItem)) ]  publicITemplateItemTemplate{ get{...} }  [ Category("Action"), Description("RaisedwhenabuttoninanItemisclicked") ]  publiceventListViewCommandEventHandlerItemCommand{ add{...} remove{...} } } 

Let's examine the metadata attributes presented in Listing 20-13:

  • DefaultEventAttribute

    Applied to the class. Allows the designer to create and wire up an event handler when the control is double-clicked on the design surface.

  • DefaultPropertyAttribute

    Applied to the class. Specifies the property that should be highlighted in the designer's property browser when the control is selected.

  • DesignerAttribute

    Applied to the class. Associates a designer with the control. We will describe the designer implementation in a moment.

  • CategoryAttribute

    Applied to properties and events. Allows the property browser to categorize properties and events into logical groupings.

  • DescriptionAttribute

    Applied to properties and events. Allows the property browser to provide short help text for each property and event.

  • DesignerSerializationVisibilityAttribute and PersistenceMode ­Attribute

    Applied to properties. Specify whether a property's value should be serialized into the .aspx file and the persistence mode used for serialization.

  • DefaultValueAttribute

    Applied to properties. Indicates the default value of the property, which allows the property browser to emphasize those properties that have been changed from their default state. This attribute also allows the control persister to determine which property values have changed and need to be persisted into the .aspx file.

  • BrowsableAttribute

    Applied to properties. Indicates whether a property should be made visible in the property browser.

  • BindableAttribute Applied to properties. Indicates whether a property should appear in the data-binding UI of the design surface.

Designer Implementation

The ListView control is associated with a custom designer by using the DesignerAttribute metadata attribute, as shown in Listing 20-13. This designer allows the ListView control to provide a rich design-time experience, including design-time data binding and template editing. We discussed implementing design-time functionality in Chapter 15. In that chapter, we showed how to implement a designer for a templated control, and in Chapter 16, we described how to implement a designer for data-bound control. The ListViewDesigner that we will now implement combines template editing with design-time data binding.

Data-Bound Control Designer

As a designer associated with a data-bound control, the ListViewDesigner enables selection of data sources in a designer and generates HTML for rendering at design time by binding the ListView control with a sample data source. We discussed the basic functionality of data-bound control designers in Chapter 16. Figure 20-2 shows the ListView control on the design surface with a WYSIWYG rendering that uses sample data.

Figure 20-2. The ListView control creating a data-bound representation in the designer

graphics/f20hn02.jpg

Listing 20-14 illustrates the core aspects of all data-bound control designers in the context of a ListView control.

Listing 20-14 Enabling data source selection and design-time data binding in ListViewDesigner
 publicclassListViewDesigner:TemplatedControlDesigner, IDataSourceProvider{ privateDataTable_dummyDataTable; privateDataTable_designTimeDataTable;  publicstringDataKeyField{  get{ return((ListView)Component).DataKeyField; } 
 set{ ((ListView)Component).DataKeyField=value; } }  publicstringDataMember{  get{ return((ListView)Component).DataMember; } set{ ((ListView)Component).DataMember=value; OnDataSourceChanged(); } }  publicstringDataSource{  get{ DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ returnbinding.Expression; } returnString.Empty; } set{ if((value==null)(value.Length==0)){ DataBindings.Remove("DataSource"); } else{ DataBindingbinding=DataBindings["DataSource"]; if(binding==null){ binding=newDataBinding("DataSource", typeof(object),value); } else{ binding.Expression=value; } DataBindings.Add(binding); } OnDataSourceChanged(); OnBindingsCollectionChanged("DataSource"); } } publicoverrideboolDesignTimeHtmlRequiresLoadComplete{ get{ //Ifwehaveadatasource,we'regoingtolookitupin //thecontainerandrequirethedocumenttobeloaded //completely. return(DataSource.Length!=0); } }  privateIEnumerableGetDesignTimeDataSource(intminimumRows){  IEnumerableselectedDataSource= ((IDataSourceProvider)this).GetResolvedSelectedDataSource(); DataTabledataTable=_designTimeDataTable; //Usethedatatablecorrespondingtotheselecteddatasource //ifpossible. if(dataTable==null){ if(selectedDataSource!=null){ _designTimeDataTable= DesignTimeData.CreateSampleDataTable(selectedDataSource); dataTable=_designTimeDataTable; } if(dataTable==null){ //Fallbackonadummydatasourceifwecan'tcreate //asampledatatable. if(_dummyDataTable==null){ _dummyDataTable= DesignTimeData.CreateDummyDataTable(); } dataTable=_dummyDataTable; } } IEnumerableliveDataSource= DesignTimeData.GetDesignTimeDataSource(dataTable, minimumRows); returnliveDataSource; }  publicoverridestringGetDesignTimeHtml(){  ListViewlv=(ListView)Component; if(lv.ItemTemplate==null){ returnGetEmptyDesignTimeHtml(); } stringdesignTimeHTML=null; IEnumerabledesignTimeDataSource=GetDesignTimeDataSource(5); booldataKeyFieldChanged=false; stringoldDataKeyField=null; try{ lv.DataSource=designTimeDataSource; oldDataKeyField=lv.DataKeyField; if(oldDataKeyField.Length!=0){ dataKeyFieldChanged=true; lv.DataKeyField=String.Empty; } lv.DataBind(); designTimeHTML=base.GetDesignTimeHtml(); } catch(Exceptione){ designTimeHTML=GetErrorDesignTimeHtml(e); } finally{ lv.DataSource=null; if(dataKeyFieldChanged){ lv.DataKeyField=oldDataKeyField; } } returndesignTimeHTML; } protectedinternalvirtualvoidOnDataSourceChanged(){ _designTimeDataTable=null; }  protectedoverridevoidPreFilterProperties(IDictionaryproperties){  base.PreFilterProperties(properties); PropertyDescriptorprop; prop=(PropertyDescriptor)properties["DataSource"]; Debug.Assert(prop!=null); //Wecan'tcreatethedesignerDataSourcepropertybasedon //therun-timepropertybecausethesetypesdonotmatch. //Therefore,wehavetocopyoveralltheattributesfromthe //runtimeandusethemthatway. AttributeCollectionruntimeAttributes=prop.Attributes; Attribute[]attrs=newAttribute[runtimeAttributes.Count+1]; runtimeAttributes.CopyTo(attrs,0); attrs[runtimeAttributes.Count]= newTypeConverterAttribute(typeof(DataSourceConverter)); prop=TypeDescriptor.CreateProperty(this.GetType(),  "DataSource",typeof(string),attrs); properties["DataSource"]=prop; prop=(PropertyDescriptor)properties["DataMember"]; Debug.Assert(prop!=null); prop=TypeDescriptor.CreateProperty(this.GetType(),prop, newAttribute[]{ newTypeConverterAttribute(typeof(DataMemberConverter)) }); properties["DataMember"]=prop; prop=(PropertyDescriptor)properties["DataKeyField"]; Debug.Assert(prop!=null); prop=TypeDescriptor.CreateProperty(this.GetType(),prop, newAttribute[]{ newTypeConverterAttribute(typeof(DataFieldConverter)) }); properties["DataKeyField"]=prop; } #regionImplementationofIDataSourceProvider  objectIDataSourceProvider.GetSelectedDataSource(){  objectselectedDataSource=null; DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ selectedDataSource= DesignTimeData.GetSelectedDataSource(Component, binding.Expression); } returnselectedDataSource; }  IEnumerableIDataSourceProvider.GetResolvedSelectedDataSource(){  IEnumerableselectedDataSource=null; DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ selectedDataSource= DesignTimeData.GetSelectedDataSource(Component, binding.Expression,DataMember); } returnselectedDataSource; } #endregion } 

The ListViewDesigner contains an implementation of the DataMember , DataKeyField , and DataSource properties. These properties are used to shadow , or replace, the corresponding properties of the runtime by overriding the PreFilterProperties method. The implementation of PreFilterProperties creates new PropertyDescriptor objects with the appropriate metadata attributes. The result of this shadowing is that the DataMember and DataSource properties that appear in the designer's property browser window are implemented by the designer, not by the selected ListView control. The DataMember and DataKeyField properties are replaced with design-time properties that are associated with custom type converters: DataMemberConverter and DataFieldConverter . These type converters are meant for design-time usage only and are therefore not directly associated with the properties on the ListView class. The DataSource property is replaced for a couple of reasons. First, the design-time property is associated with the DataSourceConverter type converter, which is also meant for design-time use only. This converter enumerates all objects on the page that can be used as a data source and presents them as choices for the page developer. In addition, the designer's DataSource property operates on the ListView control's collection of data bindings and persists the page developer's data source selection in the form of a data binding.

The ListViewDesigner also overrides the GetDesignTimeHtml method to data-bind the ListView control to a sample data source before rendering it to generate the design-time representation. ListViewDesigner encapsulates the logic to create a data source that matches the schema of the selected data source in the GetDesignTimeDataSource method. The implementation of this method uses the helper methods available in the System.Web.UI.Design.DesignTimeData class.

Template Editing

ListViewDesigner uses the template editing functionality of the design surface to allow WYSIWYG editing of its template properties. We discussed the main concepts behind template editing in Chapter 15. Figure 20-3 shows an instance of the ListView control in template editing mode.

Figure 20-3. The ListView control in template editing mode and another control with a template defined

graphics/f20hn03.jpg

Listing 20-15 contains the code from the ListViewDesigner class that implements the template editing feature of the control.

Listing 20-15 Template editing in ListViewDesigner
 publicclassListViewDesigner:TemplatedControlDesigner, IDataSourceProvider{ privateconstintHeaderFooterTemplates=0; privateconstintItemTemplates=1; privatestaticstring[]HeaderFooterTemplateNames= newstring[]{ "HeaderTemplate", "FooterTemplate" }; privateconstintHeaderTemplate=0; privateconstintFooterTemplate=1; privatestaticstring[]ItemTemplateNames= newstring[]{ "ItemTemplate", "EditItemTemplate" }; 
 privateconstintItemTemplate=0; privateconstintEditItemTemplate=1; privateTemplateEditingVerb[]_templateEditingVerbs; publicoverrideboolAllowResize{ get{ returnInTemplateMode (((ListView)Component).ItemTemplate!=null); } }  protectedoverrideITemplateEditingFrameCreateTemplateEditingFrame(  TemplateEditingVerbverb){ ITemplateEditingFrameframe=null; if((_templateEditingVerbs!=null)&& ((IList)_templateEditingVerbs).Contains(verb)){ ITemplateEditingServiceteService=(ITemplateEditingService) GetService(typeof(ITemplateEditingService)); if(teService!=null){ ListViewlv=(ListView)Component; string[]templateNames=null; Style[]templateStyles=null; switch(verb.Index){ caseHeaderFooterTemplates: templateNames=HeaderFooterTemplateNames; templateStyles= newStyle[]{ lv.HeaderStyle,lv.FooterStyle }; break; caseItemTemplates: templateNames=ItemTemplateNames; templateStyles= newStyle[]{ lv.ItemStyle,lv.EditItemStyle }; break; } frame=teService.CreateFrame(this,verb.Text, templateNames,lv.ControlStyle,templateStyles); } } returnframe; } protectedoverridevoidDispose(booldisposing){ if(disposing){ DisposeTemplateEditingVerbs(); } base.Dispose(disposing); }  privatevoidDisposeTemplateEditingVerbs(){  if(_templateEditingVerbs!=null){ for(inti=0;i<_templateEditingVerbs.Length;i++){ _templateEditingVerbs[i].Dispose(); } _templateEditingVerbs=null; } }  protectedoverride TemplateEditingVerb[]GetCachedTemplateEditingVerbs(){  if(_templateEditingVerbs==null){ _templateEditingVerbs=newTemplateEditingVerb[2]; _templateEditingVerbs[0]= newTemplateEditingVerb("HeaderandFooter", HeaderFooterTemplates,this); _templateEditingVerbs[1]= newTemplateEditingVerb("Items",ItemTemplates,this); } return_templateEditingVerbs; } publicoverridestringGetTemplateContainerDataItemProperty(stringtemplateName){ return "DataItem"; } publicoverrideIEnumerableGetTemplateContainerDataSource(stringtemplateName){ return ((IDataSourceProvider)this).GetResolvedSelectedDataSource(); }  publicoverridestringGetTemplateContent(  ITemplateEditingFrameeditingFrame,stringtemplateName, outboolallowEditing){ allowEditing=true; if((_templateEditingVerbs!=null)&& ((IList)_templateEditingVerbs).Contains(editingFrame.Verb)){ ListViewlv=(ListView)Component; ITemplatetemplate=null; switch(editingFrame.Verb.Index){ caseHeaderFooterTemplates: if(templateName.Equals(HeaderFooterTemplateNames[HeaderTemplate])){ template=lv.HeaderTemplate; } elseif(templateName.Equals(HeaderFooterTemplateNames[FooterTemplate])){ template=lv.FooterTemplate; } break; caseItemTemplates: if(templateName.Equals(ItemTemplateNames[ItemTemplate])){ template=lv.ItemTemplate; } elseif(templateName.Equals(ItemTemplateNames[EditItemTemplate])){ template=lv.EditItemTemplate; } break; } stringtemplateContent=String.Empty; if(template!=null){ templateContent=GetTextFromTemplate(template); } returntemplateContent; } returnString.Empty; } publicoverridevoidOnComponentChanged(objectsender, ComponentChangedEventArgse){ if(e.Member!=null){ stringmemberName=e.Member.Name; if(memberName.Equals("Font") memberName.Equals("ForeColor") memberName.Equals("BackColor") memberName.Equals("ItemStyle") memberName.Equals("HeaderStyle") memberName.Equals("FooterStyle") memberName.Equals("EditItemStyle")){ DisposeTemplateEditingVerbs(); } } base.OnComponentChanged(sender,e); }  publicoverridevoidSetTemplateContent(  ITemplateEditingFrameeditingFrame,stringtemplateName, stringtemplateContent){ if((_templateEditingVerbs!=null)&& ((IList)_templateEditingVerbs).Contains(editingFrame.Verb)){ ListViewlv=(ListView)Component; ITemplatenewTemplate=null; try{ newTemplate=GetTemplateFromText(templateContent); } catch{ return; } switch(editingFrame.Verb.Index){ caseHeaderFooterTemplates: if(templateName.Equals(HeaderFooterTemplateNames[HeaderTemplate])){ lv.HeaderTemplate=newTemplate; } elseif(templateName.Equals(HeaderFooterTemplateNames[FooterTemplate])){ lv.FooterTemplate=newTemplate; } break; caseItemTemplates: if(templateName.Equals(ItemTemplateNames[ItemTemplate])){ lv.ItemTemplate=newTemplate; } elseif(templateName.Equals(ItemTemplateNames[EditItemTemplate])){ lv.EditItemTemplate=newTemplate; } break; } } } } 

ListViewDesigner enables template editing by deriving from TemplatedControlDesigner and implementing its abstract methods. These methods were described in detail in Chapter 15. ListViewDesigner overrides the GetCachedTemplateEditingVerbs to create a list of TemplateEditingVerb instances. Each verb corresponds to a logical group of templates and is used to create entries in the context menu of the designer. ListViewDesigner overrides the CreateTemplateEditingFrame to create the UI associated with editing the selected TemplateEditingVerb . Finally, the ListViewDesigner overrides the GetTemplateContent and SetTemplateContent methods to provide access to the control's template text and to update the control's templates with modified template text.



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