Getting Data Source Elements
The
CreateControlHierarchy method is invoked during the binding phase to retrieve an enumerable object to use as the data source. The same method is also used to build the control's user interface when the page posts back. In this case, no
DataBind method is ever invoked. So who's the caller?
Under certain conditions,
CreateControlHierarchy is invoked from within the CreateChildControls methoda protected overridable method defined on the root Control class.
protected override void CreateChildControls()
{
// Clears all child controls
Controls.Clear();
// If the control is being redrawn because
// of a postback, restore from the ViewState
if (ViewState[ViewStateItemCount] != null)
CreateControlHierarchy(false);
}
If there's no valid information at the known location in the ViewState, the method exits; otherwise, it requires the creation of the control's hierarchy from the ViewState.
As in
Listing 3,
CreateControlHierarchy invokes a method named
GetDisplayData. This method retrieves the enumerable collection of data for building the control's user interface. The full source code of the
GetDisplayData method is shown in
Listing 5. The method always returns a non-empty collection with as many elements as there are (or there should be) items in the final control. If called from within a binding operation,
GetDisplayData just returns the contents of the
DataSource property. If called from within a ViewState load operation, the method returns a properly sized, but empty array. The size of the array is read from the ViewState and reflects the number of bars to display.
Note that most ASP.NET controls persist just one value in the ViewStatetypically, the number of rows to rebuild. This behavior is arbitrary and merely suggested by the control's architecture. There's no limitation or problem in persisting more values.
A data-bound control written in accordance with the rules and guidelines hitherto detailed offers two key benefits. First, it works as expected and survives postbacks in the most efficient way. Second, it has a neat and modular internal architecture that is fairly easy to understand, modify, and extend.
There's no need for you to add a Render method unless you want to add styling capabilities to the control.
Styling the Control
The richer a control is, the more it requires visual attributes. A realistic implementation of the BarChart control, for example, would require border, font, and colors for title, subtitle, footer, bar, and labels. Each property is fairly easy to code, but the overall number of properties would soon make it unmanageable. So what's the way out? Style properties.
A style property groups in a single object a variety of visual and graphical settings. In addition, it leaves to the control infrastructure the burden of applying style attributes to individual elements. Finally, Visual Studio .NET 2003 provides great support for styles through the expandable editor in the Properties window of the solution. Overall, reasoning in terms of styles rather than properties makes the whole approach more neat and elegant.
Here's what a style is in this context.
private TableItemStyle _titleStyle;
public virtual TableItemStyle TitleStyle
{
get
{
if (_titleStyle == null)
{
_titleStyle = new TableItemStyle();
// Can initialize the header HERE
}
if (IsTrackingViewState)
{
IStateManager ism;
ism = (IStateManager) _titleStyle;
ism.TrackViewState();
return _titleStyle;
}
}
}
The
TitleStyle property is of type
TableItemStyle and represents the visual settings for the title row. A similar property is defined for the subtitle, footer, label, and bar. For example, you can visually choose the border of the bars, the header's font, the width of the labels, and whatever else you like.
If the
TableItemStyle lacks some important properties you absolutely need, you can define your own style class.
A common mistake that developers sometimes make is styling a child control at the wrong timefor example, immediately after creation. I recommend that you use a sort of delayed styling when it comes to rendering constituent controls. When is the latest time to style a child element? When the parent control gets to render.
Here's why you now need to override the
Render method.
protected override void Render(
HtmlTextWriter output)
{
// Style controls before rendering
PrepareControlHierarchyForRendering();
// Generate markup
RenderContents(output);
}
The implementation of the
Render method is straightforward except for the call to an internal method that prepares the control for rendering. The code snippet below is excerpted from the method
PrepareControlHierarchyForRendering. It shows how to apply global settings to the control as a wholeborder, font, colorsand how to style a particular elementthe title.
// Style the control's body
Table t = (Table) Controls[0];
t.CopyBaseAttributes(this);
if (ControlStyleCreated)
t.ApplyStyle(ControlStyle);
// Style the title's cell
t.Rows[0].Cells[0].ApplyStyle(TitleStyle);
The
CopyBaseAttributes method imports all visual settings defined through the property grid of Visual Studio .NET.
 | |
| Figure 2: Editing the TitleStyle property. |
When you implement style properties, it is essential that you add the following attributes to the property's declaration.
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(
DesignerSerializationVisibility.Content)]
[NotifyParentProperty(true)]
In particular, the
PersistenceMode attribute indicates that the style will be serialized in the ASPX page markup as an inner tag named after the property. This is exactly what happens with the style properties of built-in DataGrid and DataList controls.
Figure 2 shows the property grid of the BarChart control when the user is editing the
TitleStyle property.
At this point, the BarChart control is all set and you can focus on the tricks needed to make it effectively work in a host page.