The DataBind Method
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 propertyChildControlsCreated
. 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 parameteruseDataSource
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 columnslabel
private void CreateTitle(Table t)
// Create the table row
BarChartItem item = new BarChartItem(
TableCell cell = new TableCell();
cell.ColumnSpan = BarChart.ColumnsCount;
cell.Text = Title;
// Fire HERE the ItemCreated event
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 methodsGetDisplayData
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
, 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();
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;
object o = DataBinder.Eval(dataItem,
float val = Convert.ToSingle(o);
float perc = 100*val/Maximum;
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.
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.