Implementing a Data-Bound Control


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

graphics/f16hn01.jpg

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:

  • Implements a DataSource property. DataBoundTable allows two different types of objects to be assigned to its DataSource property, as we'll describe in a moment in "The DataSource Property and Related Members ."

  • Creates its child control hierarchy by binding to the data source (in DataBind ) or by using the saved view state (in CreateChildControls ). We'll describe details in the "Creating the Control Hierarchy ” Data ­Bind and CreateChildControls " subsection.

  • Implements style-related functionality. DataBoundTable shows how to utilize typed styles and optimize performance by applying styles to child controls in the Render phase. We'll explain details in the "Styles and Rendering" subsection. Listing 16-2 does not contain code related to style functionality; you'll see that code later in this section.

    Listing 16-2 DataBoundTable.cs
     using System; using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Web.UI; using System.Web.UI.WebControls; namespace MSPress.ServerControls {     [     DefaultProperty("DataSource"),     Designer(typeof(MSPress.ServerControls.Design.DataBoundTableDesigner))     ]     public class DataBoundTable : WebControl { 
     private object _dataSource;         private TableItemStyle _itemStyle;         private TableItemStyle _altItemStyle;         private TableItemStyle _headerStyle;         #region Data source and related members          [         Category("Data"),         DefaultValue(""),         Description("The member to bind when the data source is an " +             "IListSource such as DataSet.")         ]         public virtual string DataMember {             get {                 string s = (string)ViewState["DataMember"];                 return (s == null) ? String.Empty : s;             }             set {                 ViewState["DataMember"] = value;             }         }         [         Bindable(true),         Category("Data"),         DefaultValue(null),         Description("The data source"),         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)         ]         public virtual object DataSource {             get {                 return _dataSource;             }             set {                 if ((value == null)  (value is IListSource)                       (value is IEnumerable)) {                     _dataSource = value;                 }                 else {                     throw new ArgumentException();                 }             }         } 
     protected virtual IEnumerable GetDataSource() {             if (_dataSource == null) {                 return null;             }                          IEnumerable resolvedDataSource =                  _dataSource as IEnumerable;             if (resolvedDataSource != null) {                 return resolvedDataSource;             }             IListSource listSource = _dataSource as IListSource;             if (listSource != null) {                 IList memberList = listSource.GetList();                 if (listSource.ContainsListCollection == false) {                     return (IEnumerable)memberList;                 }                 ITypedList typedMemberList = memberList as ITypedList;                 if (typedMemberList != null) {                     PropertyDescriptorCollection propDescs =                          typedMemberList.GetItemProperties(new PropertyDescriptor[0]);                     PropertyDescriptor memberProperty = null;                                          if ((propDescs != null) && (propDescs.Count != 0)) {                         string dataMember = DataMember;                         if (dataMember.Length == 0) {                             memberProperty = propDescs[0];                         }                         else {                             memberProperty =                                  propDescs.Find(dataMember, true);                         }                         if (memberProperty != null) {                             object listRow = memberList[0];                             object list =                                  memberProperty.GetValue(listRow);                             if (list is IEnumerable) {                                 return (IEnumerable)list;                             }                         } 
     throw new Exception("A list corresponding to" +                             " the selected DataMember was not found.");                     }                     throw new Exception("The selected data source " +                         "did not contain any data members to bind to.");                 }             }             return null;         }                 #endregion DataSource and related members          #region Control hierarchy and data binding          protected override void CreateChildControls() {             // CreateChildControls re-creates the children (the items)             // using the saved view state.             // First clear any existing child controls.             Controls.Clear();             // Create the items only if there is view state             // corresponding to the children.             if ((ViewState["RowCount"] != null) &&                  (ViewState["ColumnCount"] != null)) {                 CreateControlHierarchy(false);             }         }         protected virtual               void CreateControlHierarchy(bool useDataSource) {             IEnumerable dataSource = null;             int rowCount = 0;             int columnCount = 0;             if (useDataSource) {                 dataSource = GetDataSource();             }             else {                 dataSource = new object[(int)ViewState["RowCount"]];             }             if (dataSource != null) {                 Table table = new Table();                 TableRowCollection rows = table.Rows;                 Controls.Add(table); 
     PropertyDescriptor[] properties = null;                 bool createdHeader = false;                 foreach (object dataItem in dataSource) {                     if (createdHeader == false) {                         TableRow headerRow = new TableRow();                         TableCellCollection headerCells =                              headerRow.Cells;                         if (useDataSource) {                             properties =                                  GetColumnPropertyDescriptors(dataItem);                             columnCount = properties.Length;                         }                         else {                             columnCount = (int)ViewState["ColumnCount"];                         }                         for (int i = 0; i < columnCount; i++) {                             TableHeaderCell cell =                                  new TableHeaderCell();                             if (useDataSource) {                                 cell.Text = properties[i].Name;                             }                             headerCells.Add(cell);                         }                         createdHeader = true;                         rows.Add(headerRow);                     }                     TableRow row = new TableRow();                     TableCellCollection cells = row.Cells;                     for (int i = 0; i < columnCount; i++) {                         TableCell cell = new TableCell();                         if (useDataSource) {                             PropertyDescriptor pd = properties[i];                             object cellValue = pd.GetValue(dataItem);                             cell.Text =                                  (string)pd.Converter.ConvertTo(cellValue, typeof(string));                         } 
     cells.Add(cell);                     }                     rows.Add(row);                     rowCount++;                 }             }             if (useDataSource) {                 ViewState["RowCount"] = rowCount;                 ViewState["ColumnCount"] = columnCount;             }         }          public override void DataBind() {             // Data-bound controls implement custom data-binding logic              // by overriding this method.             // The following call raises the DataBinding event,              // which causes the <%# %> expressions on this control to             // be evaluated.              base.OnDataBinding(EventArgs.Empty);             // Clear the existing control hierarchy.             Controls.Clear();             // Clear any existing view state for the children because             // data binding creates a new hierarchy.              ClearChildViewState();             // Start tracking changes made during the data-binding              // process.             TrackViewState();             // Re-create the new control hierarchy using the assigned             // data source.             CreateControlHierarchy(true);             // Set the flag to indicate that child controls have been              // created so that CreateChildControls does not get called.             ChildControlsCreated = true;         }         private PropertyDescriptor[] GetColumnPropertyDescriptors(object dataItem) {             ArrayList props = new ArrayList(); 
     PropertyDescriptorCollection propDescs =                  TypeDescriptor.GetProperties(dataItem);             foreach (PropertyDescriptor pd in propDescs) {                 Type propType = pd.PropertyType;                 TypeConverter converter =                      TypeDescriptor.GetConverter(propType);                 if ((converter != null) &&                     converter.CanConvertTo(typeof(string))) {                     props.Add(pd);                 }             }             props.Sort(new PropertyDescriptorComparer());             PropertyDescriptor[] columns =                  new PropertyDescriptor[props.Count];             props.CopyTo(columns, 0);             return columns;         }                  private sealed class PropertyDescriptorComparer : IComparer {             public int Compare(object x, object y) {                 PropertyDescriptor p1 = (PropertyDescriptor)x;                 PropertyDescriptor p2 = (PropertyDescriptor)y;                 return String.Compare(p1.Name, p2.Name);             }         }         #endregion Control hierarchy and data binding                   #region Styles implementation  #endregion Styles implementation         #region Methods related to rendering  #endregion Methods related to rendering          #region Custom state management for styles  #endregion Custom state management for styles     } } 

The DataSource Property and Related Members

A 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 CreateChildControls

As 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:

  1. Invoke the OnDataBinding method so that the DataBinding event is raised and any data-binding expressions associated with your control in the .aspx page are evaluated.

  2. Clear any existing child controls because you will re-create the child control hierarchy by using the data source.

  3. Clear any view state for child controls because you will bind data to the child controls and not use the saved view state.

  4. Start tracking state in your control so that changes made while data binding are persisted in the view state. This call is needed because a page developer might invoke DataBind in the Initialize phase, before state tracking has started.

  5. Create the child control hierarchy, and bind data to the child controls. DataBoundTable implements this logic in the helper CreateControlHierarchy method, which we will explain in a moment.

  6. Set the ChildControlsCreated property to true . This indicates that CreateChildControls should not be invoked later in your control's life cycle.

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:

  1. Clear the Controls collection because you will re-create the control hierarchy by using the saved view state.

  2. Check whether any view state required to re-create the child controls was saved in the previous request. You should create the child control tree only if saved view state exists.

  3. Re-create the child controls by using the saved view state. DataBoundTable defines the CreateControlHierarchy method to create child controls. We'll explain this method next.

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 Rendering

Implementing 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 DataBoundTable
 publicclassDataBoundTable: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 :

  • Because DataBoundTable renders a table, it overrides the CreateControlStyle method to set the System.Web.UI.WebControls.TableStyle typed style as its own ControlStyle . We described how to create a new control style in the MyPanel example in Chapter 11.

  • Exposes the CellPadding , CellSpacing , and GridLines properties of the TableStyle typed style as its own top-level style properties and implements them by delegating to the corresponding properties of its associated ControlStyle .

  • Exposes three typed styles to apply to its child controls ” ItemStyle , AlternatingItemStyle , and HeaderStyle . We described how to expose typed styles in the StyledCompositeLogin example in Chapter 12.

  • Exposes the ShowHeader property, which enables the page developer to specify whether to display a header for the table.

  • Implements custom state management to track, save, and load the state of the ItemStyle , AlternatingItemStyle , and HeaderStyle typed styles. The implementation is similar to that shown in the StyledCompositeLogin example in Chapter 12. We did not include the code for state management in Listing 16-3; you can see this code in the book's sample files for this chapter.

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.



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