In this section, we'll develop the DataBoundTable control, which demonstrates the key steps in implementing a data-bound control. To keep the example simple, we will not implement support for templates in DataBoundTable . (However, we'll demonstrate that feature in the ListView control presented in Chapter 20.) DataBoundTable displays data in a tabular format, as shown in Figure 16-1. Figure 16-1. The DataBoundTable control viewed in a browser
Listing 16-1 contains a page that uses the DataBoundTable control. Listing 16-1 A page that uses the DataBoundTable control<%@ Page language="c#" %> <%@ Register TagPrefix="msp" Assembly="MSPress.ServerControls" Namespace="MSPress.ServerControls" %> <html> <script runat="server"> public class TocEntry { private int _chapterNumber; private string _chapterTitle; public TocEntry(int chapterNumber, string chapterTitle) { _chapterNumber = chapterNumber; _chapterTitle = chapterTitle; } public int Number { get { return _chapterNumber; } } public string Title { get { return _chapterTitle; } } } public void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { ArrayList a = new ArrayList(); a.Add(new TocEntry(1, ".NET Overview")); a.Add(new TocEntry(2, "Page Programming Model")); a.Add(new TocEntry(3, "Component Programming Essentials")); dataBoundTable1.DataSource = a; dataBoundTable1.DataBind(); } } </script> <body> <form method="post" runat="server" ID="Form1"> <p> <msp:DataBoundTable runat="server" id="dataBoundTable1" BackColor="LightGoldenrodYellow" ForeColor="Black" Font-Name="Verdana" Font-Size="8pt" BorderColor="Tan" BorderWidth="1px" GridLines="None" CellPadding="2"> <HeaderStyle BackColor="Tan" Font-Bold="True" /> <AlternatingItemStyle BackColor="PaleGoldenrod" /> </msp:DataBoundTable> </p> </form> </body> </html> Listing 16-2 shows the code for the DataBoundTable control. The control illustrates the following core concepts, which we'll explain after the code listing:
The DataSource Property and Related MembersA data-bound control enumerates (iterates over) a collection of data to create and bind UI elements to each enumerated item in the collection. In the .NET Framework, a collection that supports enumeration must implement the IEnumer able interface. Therefore, in its core implementation, a data-bound control binds to an IEnumerable type. It might seem obvious that the type of the DataSource property should be IEnumerable . However, declaring DataSource to be of type IEnumerable would reduce the versatility of your control, as we will soon show. When DataSource is an IEnumerable type, the type of object that the page developer assigns to this property must be an Array , a Hashtable , an ArrayList , or any other type that implements IEnumerable . However, in many data-binding scenarios, the data source is represented by a DataSet (an IListSource type), which contains a collection of DataTable instances that contain the actual data your control can bind to. To support these scenarios, your control must accept an object implementing IListSource as a data source and perform additional work to extract the actual IEnumerable object to bind to. The DataBoundTable control illustrates this scenario by showing how to implement a control that accepts two types of data sources ” IEnumerable and IListSource . DataBoundTable declares the type of the DataSource property as the generic Object type and checks that the object passed in into its property setter implements either IEnumerable or IListSource . When the DataSource property is assigned an IListSource , DataBoundTable needs to determine which list to bind to. For this purpose, DataBoundTable defines two additional members ”the DataMember property and the GetDataSource method. The DataMember property is a string that represents the name of the list within the IListSource collection (such as the name of a DataTable in a DataSet ). When the data source is an IListSource (such as a DataSet) , GetDataSource gets the name of the list (the DataTable ) from the DataMember property and returns an IEnumerable corresponding to the selected list to bind to. When the data source is an IEnumerable instance, GetDataSource simply returns the value of the DataSource property. The GetDataSource method is called from the CreateChildControls method, as we'll describe in the next subsection. The DataBoundTable example intentionally demonstrates the more complex case of a data-bound control that allows various types of data sources. If you want to restrict the data source to an IEnumerable type, you can declare the type of the DataSource property as IEnumerable without having to implement the DataMember property or the logic in the GetDataSource method. Creating the Control Hierarchy ” DataBind and CreateChildControlsAs we described in the "Data Binding Overview" section, a data-bound control uses the data source to create its control hierarchy when the page developer invokes its DataBind method. On the other hand, when the page developer does not invoke DataBind , the control re-creates its control hierarchy in the CreateChildControls method by using its saved view state. DataBoundTable illustrates the pattern for implementing these two methods. This is how DataBoundTable implements the DataBind method: publicoverridevoidDataBind(){ base.OnDataBinding(EventArgs.Empty); Controls.Clear(); ClearChildViewState(); TrackViewState(); CreateControlHierarchy(true); ChildControlsCreated=true; } The steps performed by DataBoundTable in DataBind are general, and you should perform them in any data-bound control that you implement:
The following code shows how DataBoundTable overrides the CreateChildControls method: protectedoverridevoidCreateChildControls(){ Controls.Clear(); if((ViewState["RowCount"]!=null)&& (ViewState["ColumnCount"]!=null)){ CreateControlHierarchy(false); } } The implementation of the CreateChildControls method demonstrates the steps you must perform in this method:
Because both the DataBind and CreateChildControls methods create the child control hierarchy, DataBoundTable contains a common code path to perform this task by encapsulating that logic in the helper CreateControlHierarchy method. Listing 16-2 shows how DataBoundTable implements the CreateControlHierarchy method. We recommend that you implement the pattern illustrated by this method in your own controls. The CreateControlHierarchy method defined by DataBoundTable takes a Boolean argument that indicates whether to use the data source when creating the child control hierarchy. DataBind passes true into CreateControlHierarchy , while CreateChildControls passes false into this method. When the page developer invokes DataBind on your control, DataBind invokes CreateControlHierarchy(true) . Let's examine the logic that is executed in this case. CreateControlHierarchy first invokes the GetDataSource method (described earlier in this section) to get the data source to bind to. If the data source is not null, CreateControlHierarchy creates a System.Web.UI.WebControls.Table instance because DataBoundTable renders a table. However, you can create a different container more suited to your particular scenario. CreateControlHierarchy adds the Table instance to the Controls collection. The method then inspects the first data item to infer the number of columns to create and the contents in the header. This method further enumerates over each successive data item to infer the contents of each row. CreateControlHierarchy creates System.Web.UI.WebControls.TableRow instances and then creates System.Web.UI.WebControls.TableCell (or System.Web.UI.WebControls.TableHeaderCell for the first row) instances that contain corresponding data from the data item. The method adds the cells to the row and each TableRow instance to the Table instance. Finally, the method saves the number of rows and columns in the view state. Now let's examine how DataBoundTable creates the control hierarchy on postback when the page does not invoke DataBind on the control. In this case, DataBoundTable overrides CreateChildControls to create the control hierarchy. CreateChildControls in turn invokes CreateControlHierarchy(false) . In this case, CreateControlHierarchy creates a new Table ” as in the data-binding scenario ”and adds the Table to the Controls collection. However, because a real data source does not exist, the method creates a null array whose length is the number of rows stored in the view state in the previous request. CreateControlHierarchy does not use the array to populate table cells but merely as a means for using the same iteration logic with or without a real data source. CreateControlHierarchy obtains the number of columns from the value stored in the view state in the previous request. The method successively creates new TableRow s, adds TableCell s (or TableHeaderCell s) to each TableRow , and adds each row to the Table instance. Because the Table instance is already added to the control hierarchy, as soon as a TableRow is added to the Table , the page framework loads in the view state that was saved during a previous request. This completes the creation of the control hierarchy by using the saved view state. (In Chapter 12, we described how view state is loaded into a child control in the Load View State phase or whenever a child control is added to the control hierarchy after this phase.) Styles and RenderingImplementing styles in a data-bound control is not essential. However, styles govern the appearance of data and thus offer significant customization capabilities to the page developer. For example, the DataBoundTable shown in Figure 16-1 can display the header differently from the data and mark alternating items by using typed styles. We described typed styles in Chapter 11, "Styles in Controls," and we showed how to create and apply typed styles to child controls in Chapter 12. DataBoundTable uses the concepts explained in those two chapters. Listing 16-3 shows how DataBoundTable implements styles. Listing 16-3 Styles implementation in DataBoundTablepublicclassDataBoundTable:WebControl{ privateTableItemStyle_itemStyle; privateTableItemStyle_altItemStyle; privateTableItemStyle_headerStyle; #regionDataSource,controlhierarchy,databinding #endregionDataSource,controlhierarchy,databinding #regionStylesimplementation [ Category("Appearance"), DefaultValue(-1), Description("") ] publicvirtualintCellPadding{ get{ if(ControlStyleCreated==false){ return-1; } return((TableStyle)ControlStyle).CellPadding; } set{ ((TableStyle)ControlStyle).CellPadding=value; } } [ Category("Appearance"), DefaultValue(0), Description("") ] publicvirtualintCellSpacing{ get{ if(ControlStyleCreated==false){ return0; } return((TableStyle)ControlStyle).CellSpacing; } set{ ((TableStyle)ControlStyle).CellSpacing=value; } } [ Category("Appearance"), DefaultValue(GridLines.None), Description("") ] publicvirtualGridLinesGridLines{ get{ if(ControlStyleCreated==false){ returnGridLines.None; } return((TableStyle)ControlStyle).GridLines; } set{ ((TableStyle)ControlStyle).GridLines=value; } } [ Category("Style"), Description(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ] publicTableItemStyleAlternatingItemStyle{ get{ if(_altItemStyle==null){ _altItemStyle=newTableItemStyle(); if(IsTrackingViewState){ ((IStateManager)_altItemStyle).TrackViewState(); } } return_altItemStyle; } } [ Category("Style"), Description(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ] publicTableItemStyleHeaderStyle{ get{ if(_headerStyle==null){ _headerStyle=newTableItemStyle(); if(IsTrackingViewState){ ((IStateManager)_headerStyle).TrackViewState(); } } return_headerStyle; } } [ Category("Style"), Description(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty) ] publicTableItemStyleItemStyle{ get{ if(_itemStyle==null){ _itemStyle=newTableItemStyle(); if(IsTrackingViewState){ ((IStateManager)_itemStyle).TrackViewState(); } } return_itemStyle; } } [ Bindable(true), Category("Appearance"), DefaultValue(true), Description("") ] publicvirtualboolShowHeader{ get{ objectb=ViewState["ShowHeader"]; return(b==null)?true:(bool)b; } set{ ViewState["ShowHeader"]=value; } } protectedoverrideStyleCreateControlStyle(){ //BecausetheDataBoundTablerendersanHTMLtable, //aninstanceofaTableStyleisusedasthecontrolstyle. TableStylestyle=newTableStyle(ViewState); //Initializethestyle. style.CellSpacing=0; style.GridLines=GridLines.Both; returnstyle; } #endregionStylesimplementation #regionMethodsrelatedtorendering protectedvirtualvoidPrepareControlHierarchyForRendering(){ ControlCollectioncontrols=Controls; if(controls.Count!=1){ return; } Tabletable=(Table)controls[0]; table.CopyBaseAttributes(this); if(ControlStyleCreated){ table.ApplyStyle(ControlStyle); } else{ //Becausewehaven'tcreatedaControlStyleyet,the //defaultsettingsforthedefaultstyleofthecontrol //needtobeappliedtothechildtablecontroldirectly. table.CellSpacing=0; table.GridLines=GridLines.Both; } TableItemStylealtStyle=_itemStyle; if(_altItemStyle!=null){ altStyle=newTableItemStyle(); altStyle.CopyFrom(_itemStyle); altStyle.CopyFrom(_altItemStyle); } boolexpectingHeader=true; introwIndex=0; foreach(TableRowrowintable.Rows){ if(expectingHeader){ expectingHeader=false; boolshowHeader=ShowHeader; row.Visible=showHeader; if(showHeader&&(_headerStyle!=null)){ row.ApplyStyle(_headerStyle); } } else{ TableItemStylestyle=_itemStyle; if(((rowIndex+1)%2)==0){ style=altStyle; } if(style!=null){ row.ApplyStyle(style); } } rowIndex++; } } protectedoverridevoidRender(HtmlTextWriterwriter){ //Applystylestothecontrolhierarchy,andthenrender //thechildcontrols. //StylesareappliedintheRenderphasesothat: //a)Thepagedevelopercanchangestylesaftercalling //DataBind. //b)Changesmadetoitemswhenapplyingstylesdonot //contributetotheviewstate.Thiscontrolmanages //thestateforstyles,sohavingitemsmanageitas //wellwouldberedundant. PrepareControlHierarchyForRendering(); //Wedon'trenderatop-leveltagforourcontrolbecause //thechildTablecontrolprovidesthattag.Therefore, //insteadofcallingbase.Render,wecallRenderContents. RenderContents(writer); } #endregionMethodsrelatedtorendering #regionCustomstatemanagementforstyles #endregionCustomstatemanagementforstyles } DataBoundTable performs the following style-related tasks :
DataBoundTable applies styles to child controls in the Render phase so that changes caused by applied styles are not persisted in the view state of its child control. As we explained in Chapter 12, this ensures that changes are not persisted twice ”in the view state of the parent control and in the view state of the child controls. DataBoundTable defines the helper PrepareControlHier archyForRendering method, which applies styles to child controls. |