The DataBind Method
Listing 2 shows the typical implementation of the
DataBind method in a data-bound control. To start off, the method invokes itself on the base class. Next, it clears the collection of child controls and their ViewState. When the
DataBind method is called, data-bound controls get fresh data and update their user interface. When this happens, it's a good and safe habit to clean up child controls and memory.
The control's ViewState is reset and an internal engine that tracks changes to the control's ViewState is turned on. (This is what
TrackViewState really does.) Finally, the control builds the user interface.
The user interface of a typical composite control consists of a hierarchy of child controls. The method
CreateControlHierarchy creates this hierarchy for the BarChart control. In ASP.NET 1.x,
CreateControlHierarchy is not a framework method to override; however, most data-bound controls define it internally as a protected and virtual member. In this way, inherited controls can easily override it thus modifying the rendering of the control.
Listing 3 shows the source code of the method.
By design, when
CreateControlHierarchy returns all child controls have been created. This situation is notified to base classes through a writeable Boolean property
ChildControlsCreated. Defined on the Control class, the property just indicates whether the server control's child controls have been created.
Building the Control's User Interface
As
Listing 3 shows, the
CreateControlHierarchy method accepts a Boolean argument that the
DataBind method sets to true. What's the role of this argument? Some hints come through the formal name of the parameter
useDataSource.
CreateControlHierarchy gets an enumerable collection of data and then starts building the BarChart's hierarchy of child controls. The BarChart control consists of an outermost table with a bunch of rows. The first row contains the title of the chart; the second row shows the subtitle. These items are not data-bound meaning that the rendering of these components is not influenced by the data source contents. The following code snippet demonstrates the creation of the title row. The row contains a single cell that spans over the number of columns. The BarChart control contains two columns
label and
value.
private void CreateTitle(Table t)
{
// Create the table row
BarChartItem item = new BarChartItem(
BarChartItemType.Title);
t.Rows.Add(item);
TableCell cell = new TableCell();
cell.ColumnSpan = BarChart.ColumnsCount;
cell.Text = Title;
//
// Fire HERE the ItemCreated event
item.Cells.Add(cell);
}
Next, the
CreateControlHierarchy method creates all data-bound items and completes the structure with the footer row.
Listing 4 details the creation of all data-bound items.
The Boolean argument originally passed to
CreateControlHierarchy is forwarded to a couple of internal methods
GetDisplayData first and
CreateBarChartItem next. The Boolean argument indicates how to build the control's infrastructurefrom the data source or the ViewState. In other words, if the parameter
useDataSource is
true, the
DataSource property is set to a non-null value; otherwise, the data source is empty and the BarChart control must rebuild itself from the ViewState.
Before going any further with more details, I need to bear down on the reasons that determine the value of the
useDataSource parameter. When a data-bound control is first populatedthat is, a call to
DataBind is madethe parameter is set to
true and a valid data source object is available.
When the host page posts back, the control needs to redraw itself but the data source may not be there. If the sender of the postback is the control itself, you handle one of its events and rebind fresh data. But what if the sender of the postback is another control in the page?
To minimize the workload, the control should not be re-bound to data if the data to display hasn't changed. So how can the control refresh?
The page ViewState doesn't contain the whole data source as a separate object, but each and every constituent control saved its visual properties to the ViewState. To make the BarChart data-bound control survive a postback, you must write its rendering code so that the phase of creation of constituent controls is separated from the data binding phase. In this way, the structure is correctly recreated after each postback and the binding takes place only if necessary. In addition, each constituent controltable cells, labels, textboxeswill automatically restore themselves from the ViewState thus recreating the overall interface. Let me illustrate this with an example.
You know from
Figure 1 that the BarChart control is made of a collection of rows. Each row has two cells, the second of which is generated by the following code.
Label lblGraph = new Label();
Label lblText = new Label();
cell.Controls.Add(lblGraph);
cell.Controls.Add(new LiteralControl("<br>"));
cell.Controls.Add(lblText);
The horizontal bar is rendered through a Label control whose Width property is set to a percentage that represents the associated value. The Width property is assigned at binding time:
Label lblGraph = (Label) cell.Controls[0];
object o = DataBinder.Eval(dataItem,
DataValueField);
float val = Convert.ToSingle(o);
float perc = 100*val/Maximum;
lblGraph.Width =Unit.Percentage(perc);
When the page with the BarChart control posts back, what really matters is that the Label control be recreated in the same place. The Label control will then restore itself from the ViewState and maintains the assigned width.
The number of bars depends on the bound data source. When there's no data sourcethat is, after a postbackhow can you determine the correct number of bars that need to be displayed? By design, this information is known only at binding time and to access it later you must save it somewhere. Where? In the ViewState.
if (useDataSource)
ViewState[ViewStateItemCount] = itemCount;
This trick is widely used in most ASP.NET built-in controls (e.g., DataGrid) and sets a clear dependency between these controls and the ViewState. For the trick to work, though, it is essential that you add each newly created control to its parent in the hierarchy as soon as possibleideally, right after instantiation.