The TemplatedList control described in the following sample is a simplified version of the System.Web.UI.DataList control. It illustrates the following concepts.
- Data binding, as described in Developing a Templated Data-Bound Control.
DataSourceis the data source forTemplatedList.ItemTemplateis the template property ofTemplatedList.TemplatedListItemis the logical container forItemTemplate.TemplatedListItemhas a property,DataItem, that binds to each element ofDataSource. - Event bubbling, as described in Bubbling an Event.
TemplatedListItembubbles command events from controls in the template toTemplatedList. TemplatedListexposes a bubbled event as anItemCommandevent. TheTemplatedListCommandEventArgsclass contains the data for the event. - Exposing styles from a control as described in Styles in Server Controls.
TemplatedListexposes theItemStyle,AlternatingItemStyle, andSelectedItemStyleproperties of type System.Web.UI.WebControls.TableStyle. - Customizing state restoration using ViewState.
TemplatedListoverrides the SaveViewState and LoadViewState methods inherited from Control to customize the restoration of its Style properties. - Applying metadata attributes for design-time support. For information about design-time attributes, see Attributes and Design-Time Support. The designer (
CustomControls.Design.TemplatedListDesigner) specified in the DesignerAttribute is implemented in the Web Forms Templated Data-Bound Control Designer Sample.
To build the sample, see the instructions in Server Control Samples.
// TemplatedList.cs. namespace CustomControls { using System; using System.ComponentModel; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.Collections; using System.Diagnostics; using System.Web.UI; using System.Web.UI.WebControls; [ DefaultEvent("SelectedIndexChanged"), DefaultProperty("DataSource"), Designer("CustomControls.Design.TemplatedListDesigner, CustomControls.Design", typeof(IDesigner)) ] public class TemplatedList : WebControl, INamingContainer { #region Statics and Constants private static readonly object EventSelectedIndexChanged = new object(); private static readonly object EventItemCreated = new object(); private static readonly object EventItemDataBound = new object(); private static readonly object EventItemCommand = new object(); #endregion #region Member variables private IEnumerable dataSource; private TableItemStyle itemStyle; private TableItemStyle alternatingItemStyle; private TableItemStyle selectedItemStyle; private ITemplate itemTemplate; #endregion #region Properties [ Category("Style"), Description("The style to be applied to alternate items."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty), ] public virtual TableItemStyle AlternatingItemStyle { get { if (alternatingItemStyle == null) { alternatingItemStyle = new TableItemStyle(); if (IsTrackingViewState) ((IStateManager)alternatingItemStyle).TrackViewState(); } return alternatingItemStyle; } } [ Bindable(true), Category("Appearance"), DefaultValue(-1), Description("The cell padding of the rendered table.") ] public virtual int CellPadding { get { if (ControlStyleCreated == false) { return -1; } return ((TableStyle)ControlStyle).CellPadding; } set { ((TableStyle)ControlStyle).CellPadding = value; } } [ Bindable(true), Category("Appearance"), DefaultValue(0), Description("The cell spacing of the rendered table.") ] public virtual int CellSpacing { get { if (ControlStyleCreated == false) { return 0; } return ((TableStyle)ControlStyle).CellSpacing; } set { ((TableStyle)ControlStyle).CellSpacing = value; } } [ Bindable(true), Category("Data"), DefaultValue(null), Description("The data source used to build up the control."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) ] public IEnumerable DataSource { get { return dataSource; } set { dataSource = value; } } [ Bindable(true), Category("Appearance"), DefaultValue(GridLines.None), Description("The grid lines to be shown in the rendered table.") ] public virtual GridLines GridLines { get { if (ControlStyleCreated == false) { return GridLines.None; } return ((TableStyle)ControlStyle).GridLines; } set { ((TableStyle)ControlStyle).GridLines = value; } } [ Category("Style"), Description("The style to be applied to all items."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty), ] public virtual TableItemStyle ItemStyle { get { if (itemStyle == null) { itemStyle = new TableItemStyle(); if (IsTrackingViewState) ((IStateManager)itemStyle).TrackViewState(); } return itemStyle; } } [ Browsable(false), DefaultValue(null), Description("The content to be shown in each item."), PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(TemplatedListItem)) ] public virtual ITemplate ItemTemplate { get { return itemTemplate; } set { itemTemplate = value; } } [ Bindable(true), DefaultValue(-1), Description("The index of the selected item.") ] public virtual int SelectedIndex { get { object o = ViewState["SelectedIndex"]; if (o != null) return(int)o; return -1; } set { if (value < -1) { throw new ArgumentOutOfRangeException(); } int oldSelectedIndex = SelectedIndex; ViewState["SelectedIndex"] = value; if (HasControls()) { Table table = (Table)Controls[0]; TemplatedListItem item; if ((oldSelectedIndex != -1) && (table.Rows.Count > oldSelectedIndex)) { item = (TemplatedListItem)table.Rows[oldSelectedIndex]; if (item.ItemType != ListItemType.EditItem) { ListItemType itemType = ListItemType.Item; if (oldSelectedIndex % 2 != 0) itemType = ListItemType.AlternatingItem; item.SetItemType(itemType); } } if ((value != -1) && (table.Rows.Count > value)) { item = (TemplatedListItem)table.Rows[value]; item.SetItemType(ListItemType.SelectedItem); } } } } [ Category("Style"), Description("The style to be applied to the selected item."), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true), PersistenceMode(PersistenceMode.InnerProperty), ] public virtual TableItemStyle SelectedItemStyle { get { if (selectedItemStyle == null) { selectedItemStyle = new TableItemStyle(); if (IsTrackingViewState) ((IStateManager)selectedItemStyle).TrackViewState(); } return selectedItemStyle; } } #endregion #region Events protected virtual void OnItemCommand(TemplatedListCommandEventArgs e) { TemplatedListCommandEventHandler onItemCommandHandler = (TemplatedListCommandEventHandler)Events[EventItemCommand]; if (onItemCommandHandler != null) onItemCommandHandler(this, e); } protected virtual void OnItemCreated(TemplatedListItemEventArgs e) { TemplatedListItemEventHandler onItemCreatedHandler = (TemplatedListItemEventHandler)Events[EventItemCreated]; if (onItemCreatedHandler != null) onItemCreatedHandler(this, e); } protected virtual void OnItemDataBound(TemplatedListItemEventArgs e) { TemplatedListItemEventHandler onItemDataBoundHandler = (TemplatedListItemEventHandler)Events[EventItemDataBound]; if (onItemDataBoundHandler != null) onItemDataBoundHandler(this, e); } protected virtual void OnSelectedIndexChanged(EventArgs e) { EventHandler handler = (EventHandler)Events[EventSelectedIndexChanged]; if (handler != null) handler(this, e); } [ Category("Action"), Description("Raised when a CommandEvent occurs within an item.") ] public event TemplatedListCommandEventHandler ItemCommand { add { Events.AddHandler(EventItemCommand, value); } remove { Events.RemoveHandler(EventItemCommand, value); } } [ Category("Behavior"), Description("Raised when an item is created and is ready for customization.") ] public event TemplatedListItemEventHandler ItemCreated { add { Events.AddHandler(EventItemCreated, value); } remove { Events.RemoveHandler(EventItemCreated, value); } } [ Category("Behavior"), Description("Raised when an item is data-bound.") ] public event TemplatedListItemEventHandler ItemDataBound { add { Events.AddHandler(EventItemDataBound, value); } remove { Events.RemoveHandler(EventItemDataBound, value); } } [ Category("Action"), Description("Raised when the SelectedIndex property has changed.") ] public event EventHandler SelectedIndexChanged { add { Events.AddHandler(EventSelectedIndexChanged, value); } remove { Events.RemoveHandler(EventSelectedIndexChanged, value); } } #endregion #region Methods and Implementation protected override void CreateChildControls() { Controls.Clear(); if (ViewState["ItemCount"] != null) { // Create the control hierarchy using the view state, // not the data source. CreateControlHierarchy(false); } } private void CreateControlHierarchy(bool useDataSource) { IEnumerable dataSource = null; int count = -1; if (useDataSource == false) { // ViewState must have a non-null value for ItemCount because this is checked // by CreateChildControls. count = (int)ViewState["ItemCount"]; if (count != -1) { dataSource = new DummyDataSource(count); } } else { dataSource = this.dataSource; } if (dataSource != null) { Table table = new Table(); Controls.Add(table); int selectedItemIndex = SelectedIndex; int index = 0; count = 0; foreach (object dataItem in dataSource) { ListItemType itemType = ListItemType.Item; if (index == selectedItemIndex) { itemType = ListItemType.SelectedItem; } else if (index % 2 != 0) { itemType = ListItemType.AlternatingItem; } CreateItem(table, index, itemType, useDataSource, dataItem); count++; index++; } } if (useDataSource) { // Save the number of items contained for use in round trips. ViewState["ItemCount"] = ((dataSource != null) ? count : -1); } } protected override Style CreateControlStyle() { // Since the TemplatedList control renders an HTML table, // an instance of the TableStyle class is used as the control style. TableStyle style = new TableStyle(ViewState); // Set up default initial state. style.CellSpacing = 0; return style; } private TemplatedListItem CreateItem(Table table, int itemIndex, ListItemType itemType, bool dataBind, object dataItem) { TemplatedListItem item = new TemplatedListItem(itemIndex, itemType); TemplatedListItemEventArgs e = new TemplatedListItemEventArgs(item); if (itemTemplate != null) { itemTemplate.InstantiateIn(item.Cells[0]); } if (dataBind) { item.DataItem = dataItem; } OnItemCreated(e); table.Rows.Add(item); if (dataBind) { item.DataBind(); OnItemDataBound(e); item.DataItem = null; } return item; } public override void DataBind() { // Controls with a data-source property perform their custom data binding // by overriding DataBind. // Evaluate any data-binding expressions on the control itself. base.OnDataBinding(EventArgs.Empty); // Reset the control state. Controls.Clear(); ClearChildViewState(); // Create the control hierarchy using the data source. CreateControlHierarchy(true); ChildControlsCreated = true; TrackViewState(); } protected override void LoadViewState(object savedState) { // Customize state management to handle saving state of contained objects. if (savedState != null) { object[] myState = (object[])savedState; if (myState[0] != null) base.LoadViewState(myState[0]); if (myState[1] != null) ((IStateManager)ItemStyle).LoadViewState(myState[1]); if (myState[2] != null) ((IStateManager)SelectedItemStyle).LoadViewState(myState[2]); if (myState[3] != null) ((IStateManager)AlternatingItemStyle).LoadViewState(myState[3]); } } protected override bool OnBubbleEvent(object source, EventArgs e) { // Handle events raised by children by overriding OnBubbleEvent. bool handled = false; if (e is TemplatedListCommandEventArgs) { TemplatedListCommandEventArgs ce = (TemplatedListCommandEventArgs)e; OnItemCommand(ce); handled = true; if (String.Compare(ce.CommandName, "Select", true) == 0) { SelectedIndex = ce.Item.ItemIndex; OnSelectedIndexChanged(EventArgs.Empty); } } return handled; } private void PrepareControlHierarchy() { if (HasControls() == false) { return; } Debug.Assert(Controls[0] is Table); Table table = (Table)Controls[0]; table.CopyBaseAttributes(this); if (ControlStyleCreated) { table.ApplyStyle(ControlStyle); } // The composite alternating item style; do just one // merge style on the actual item. Style altItemStyle = null; if (alternatingItemStyle != null) { altItemStyle = new TableItemStyle(); altItemStyle.CopyFrom(itemStyle); altItemStyle.CopyFrom(alternatingItemStyle); } else { altItemStyle = itemStyle; } int rowCount = table.Rows.Count; for (int i = 0; i < rowCount; i++) { TemplatedListItem item = (TemplatedListItem)table.Rows[i]; Style compositeStyle = null; switch (item.ItemType) { case ListItemType.Item: compositeStyle = itemStyle; break; case ListItemType.AlternatingItem: compositeStyle = altItemStyle; break; case ListItemType.SelectedItem: { compositeStyle = new TableItemStyle(); if (item.ItemIndex % 2 != 0) compositeStyle.CopyFrom(altItemStyle); else compositeStyle.CopyFrom(itemStyle); compositeStyle.CopyFrom(selectedItemStyle); } break; } if (compositeStyle != null) { item.MergeStyle(compositeStyle); } } } protected override void Render(HtmlTextWriter writer) { // Apply styles to the control hierarchy // and then render it out. // Apply styles during render phase, so the user can change styles // after calling DataBind without the property changes ending // up in view state. PrepareControlHierarchy(); RenderContents(writer); } protected override object SaveViewState() { // Customized state management to handle saving state of contained objects such as styles. object baseState = base.SaveViewState(); object itemStyleState = (itemStyle != null) ? ((IStateManager)itemStyle).SaveViewState() : null; object selectedItemStyleState = (selectedItemStyle != null) ? ((IStateManager)selectedItemStyle).SaveViewState() : null; object alternatingItemStyleState = (alternatingItemStyle != null) ? ((IStateManager)alternatingItemStyle).SaveViewState() : null; object[] myState = new object[4]; myState[0] = baseState; myState[1] = itemStyleState; myState[2] = selectedItemStyleState; myState[3] = alternatingItemStyleState; return myState; } protected override void TrackViewState() { // Customized state management to handle saving state of contained objects such as styles. base.TrackViewState(); if (itemStyle != null) ((IStateManager)itemStyle).TrackViewState(); if (selectedItemStyle != null) ((IStateManager)selectedItemStyle).TrackViewState(); if (alternatingItemStyle != null) ((IStateManager)alternatingItemStyle).TrackViewState(); } #endregion } public class TemplatedListItem : TableRow, INamingContainer { private int itemIndex; private ListItemType itemType; private object dataItem; public TemplatedListItem(int itemIndex, ListItemType itemType) { this.itemIndex = itemIndex; this.itemType = itemType; Cells.Add(new TableCell()); } public virtual object DataItem { get { return dataItem; } set { dataItem = value; } } public virtual int ItemIndex { get { return itemIndex; } } public virtual ListItemType ItemType { get { return itemType; } } protected override bool OnBubbleEvent(object source, EventArgs e) { if (e is CommandEventArgs) { // Add the information about Item to CommandEvent. TemplatedListCommandEventArgs args = new TemplatedListCommandEventArgs(this, source, (CommandEventArgs)e); RaiseBubbleEvent(this, args); return true; } return false; } internal void SetItemType(ListItemType itemType) { this.itemType = itemType; } } public sealed class TemplatedListCommandEventArgs : CommandEventArgs { private TemplatedListItem item; private object commandSource; public TemplatedListCommandEventArgs(TemplatedListItem item, object commandSource, CommandEventArgs originalArgs) : base(originalArgs) { this.item = item; this.commandSource = commandSource; } public TemplatedListItem Item { get { return item; } } public object CommandSource { get { return commandSource; } } } public delegate void TemplatedListCommandEventHandler(object source, TemplatedListCommandEventArgs e); public sealed class TemplatedListItemEventArgs : EventArgs { private TemplatedListItem item; public TemplatedListItemEventArgs(TemplatedListItem item) { this.item = item; } public TemplatedListItem Item { get { return item; } } } public delegate void TemplatedListItemEventHandler(object sender, TemplatedListItemEventArgs e); internal sealed class DummyDataSource : ICollection { private int dataItemCount; public DummyDataSource(int dataItemCount) { this.dataItemCount = dataItemCount; } public int Count { get { return dataItemCount; } } public bool IsReadOnly { get { return false; } } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return this; } } public void CopyTo(Array array, int index) { for (IEnumerator e = this.GetEnumerator(); e.MoveNext();) array.SetValue(e.Current, index++); } public IEnumerator GetEnumerator() { return new DummyDataSourceEnumerator(dataItemCount); } private class DummyDataSourceEnumerator : IEnumerator { private int count; private int index; public DummyDataSourceEnumerator(int count) { this.count = count; this.index = -1; } public object Current { get { return null; } } public bool MoveNext() { index++; return index < count; } public void Reset() { this.index = -1; } } } } [Visual Basic] ' TemplatedList.vb Imports System Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.ComponentModel.Design.Serialization Imports System.Collections Imports System.Diagnostics Imports System.Web.UI Imports System.Web.UI.WebControls Namespace CustomControls <DefaultEvent("SelectedIndexChanged"), _ DefaultProperty("DataSource"), _ Designer("CustomControls.Design.TemplatedListDesigner, CustomControls.Design", _ GetType(IDesigner))> _ Public Class TemplatedList Inherits WebControl Implements INamingContainer #Region "Statics and Constants" Private Shared EventSelectedIndexChanged As New Object() Private Shared EventItemCreated As New Object() Private Shared EventItemDataBound As New Object() Private Shared EventItemCommand As New Object() #End Region #Region "Member variables" Private _dataSource As IEnumerable Private _itemStyle As TableItemStyle ' Private _alternatingItemStyle As TableItemStyle Private _selectedItemStyle As TableItemStyle Private _itemTemplate As ITemplate #End Region #Region "Properties" <Category("Style"), _ Description("The style to be applied to alternate items."), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ NotifyParentProperty(True), _ PersistenceMode(PersistenceMode.InnerProperty)> _ Public Overridable ReadOnly Property AlternatingItemStyle() As TableItemStyle Get If _alternatingItemStyle Is Nothing Then _alternatingItemStyle = New TableItemStyle() If IsTrackingViewState Then CType(_alternatingItemStyle, IStateManager).TrackViewState() End If End If Return _alternatingItemStyle End Get End Property <Bindable(True), _ Category("Appearance"), _ DefaultValue(- 1), _ Description("The cell padding of the rendered table.")> _ Public Overridable Property CellPadding() As Integer Get If ControlStyleCreated = False Then Return - 1 End If Return CType(ControlStyle, TableStyle).CellPadding End Get Set CType(ControlStyle, TableStyle).CellPadding = value End Set End Property <Bindable(True), _ Category("Appearance"), _ DefaultValue(0), _ Description("The cell spacing of the rendered table.")> _ Public Overridable Property CellSpacing() As Integer Get If ControlStyleCreated = False Then Return 0 End If Return CType(ControlStyle, TableStyle).CellSpacing End Get Set CType(ControlStyle, TableStyle).CellSpacing = value End Set End Property <Bindable(True), _ Category("Data"), _ Description("The data source used to build up the control."), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _ Public Property DataSource() As IEnumerable Get Return _dataSource End Get Set _dataSource = value End Set End Property <Bindable(True), _ Category("Appearance"), _ DefaultValue(System.Web.UI.WebControls.GridLines.None), _ Description("The grid lines to be shown in the rendered table.")> _ Public Overridable Property GridLines() As GridLines Get If ControlStyleCreated = False Then Return GridLines.None End If Return CType(ControlStyle, TableStyle).GridLines End Get Set CType(ControlStyle, TableStyle).GridLines = value End Set End Property <Category("Style"), _ Description("The style to be applied to all items."), _ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _ NotifyParentProperty(True), _ PersistenceMode(PersistenceMode.InnerProperty)> _ Public Overridable ReadOnly Property ItemStyle() As TableItemStyle Get If _itemStyle Is Nothing Then _itemStyle = New TableItemStyle() If IsTrackingViewState Then CType(_itemStyle, IStateManager).TrackViewState() End If End If Return _itemStyle End Get End Property <Browsable(False), _ Description("The content to be shown in each item."), _ PersistenceMode(PersistenceMode.InnerProperty), _ TemplateContainer(GetType(TemplatedListItem))> _ Public Overridable Property ItemTemplate() As ITemplate Get Return _itemTemplate End Get Set _itemTemplate = value End Set End Property <Bindable(True), _ DefaultValue(- 1), _ Description("The index of the selected item.")> _ Public Overridable Property SelectedIndex() As Integer Get Dim o As Object = ViewState("SelectedIndex") If Not (o Is Nothing) Then Return CInt(o) End If Return - 1 End Get Set If value < - 1 Then Throw New ArgumentOutOfRangeException() End If Dim oldSelectedIndex As Integer = SelectedIndex ViewState("SelectedIndex") = value If HasControls() Then Dim table As Table = CType(Controls(0), Table) Dim item As TemplatedListItem If oldSelectedIndex <> - 1 And table.Rows.Count > oldSelectedIndex Then item = CType(table.Rows(oldSelectedIndex), TemplatedListItem) If item.ItemType <> ListItemType.EditItem Then Dim itemType As ListItemType = ListItemType.Item If oldSelectedIndex Mod 2 <> 0 Then itemType = ListItemType.AlternatingItem End If item.SetItemType(itemType) End If End If If value <> - 1 And table.Rows.Count > value Then item = CType(table.Rows(value), TemplatedListItem) item.SetItemType(ListItemType.SelectedItem) End If End If End Set End Property <Category("Style"), _ Description("The style to be applied to the selected item."), _ DesignerSerializationVisibility( _ DesignerSerializationVisibility.Content), _ NotifyParentProperty(True), _ PersistenceMode(PersistenceMode.InnerProperty)> _ Public Overridable ReadOnly Property SelectedItemStyle() As TableItemStyle Get If _selectedItemStyle Is Nothing Then _selectedItemStyle = New TableItemStyle() If IsTrackingViewState Then CType(_selectedItemStyle, IStateManager).TrackViewState() End If End If Return _selectedItemStyle End Get End Property #End Region #Region "Events" Protected Overridable Sub OnItemCommand(e As TemplatedListCommandEventArgs) Dim onItemCommandHandler As TemplatedListCommandEventHandler = CType(Events(EventItemCommand), TemplatedListCommandEventHandler) If Not (onItemCommandHandler Is Nothing) Then onItemCommandHandler(Me, e) End If End Sub Protected Overridable Sub OnItemCreated(e As TemplatedListItemEventArgs) Dim onItemCreatedHandler As TemplatedListItemEventHandler = CType(Events(EventItemCreated), TemplatedListItemEventHandler) If Not (onItemCreatedHandler Is Nothing) Then onItemCreatedHandler(Me, e) End If End Sub Protected Overridable Sub OnItemDataBound(e As TemplatedListItemEventArgs) Dim onItemDataBoundHandler As TemplatedListItemEventHandler = CType(Events(EventItemDataBound), TemplatedListItemEventHandler) If Not (onItemDataBoundHandler Is Nothing) Then onItemDataBoundHandler(Me, e) End If End Sub Protected Overridable Sub OnSelectedIndexChanged(e As EventArgs) Dim handler As EventHandler = CType(Events(EventSelectedIndexChanged), EventHandler) If Not (handler Is Nothing) Then handler(Me, e) End If End Sub <Category("Action"), _ Description("Raised when a CommandEvent occurs within an item.")> _ Public Event ItemCommand As TemplatedListCommandEventHandler <Category("Behavior"), _ Description("Raised when an item is created and is ready for customization.")> _ Public Event ItemCreated As TemplatedListItemEventHandler <Category("Behavior"), _ Description("Raised when an item is data-bound.")> _ Public Event ItemDataBound As TemplatedListItemEventHandler <Category("Action"), _ Description("Raised when the SelectedIndex property has changed.")> _ Public Event SelectedIndexChanged As EventHandler #End Region #Region "Methods and Implementation" Protected Overrides Sub CreateChildControls() Controls.Clear() If Not (ViewState("ItemCount") Is Nothing) Then ' Create the control hierarchy using the view state, ' not the data source. CreateControlHierarchy(False) End If End Sub Private Sub CreateControlHierarchy(useDataSource As Boolean) Dim dataSource As IEnumerable = Nothing Dim count As Integer = - 1 If useDataSource = False Then ' ViewState must have a non-null value for ItemCount because this is checked ' in CreateChildControls. count = CInt(ViewState("ItemCount")) If count <> - 1 Then dataSource = New DummyDataSource(count) End If Else dataSource = Me._dataSource End If If Not (dataSource Is Nothing) Then Dim table As New Table() Controls.Add(table) Dim selectedItemIndex As Integer = SelectedIndex Dim index As Integer = 0 count = 0 Dim dataItem As Object For Each dataItem In dataSource Dim itemType As ListItemType = ListItemType.Item If index = selectedItemIndex Then itemType = ListItemType.SelectedItem Else If index Mod 2 <> 0 Then itemType = ListItemType.AlternatingItem End If End If CreateItem(table, index, itemType, useDataSource, dataItem) count += 1 index += 1 Next dataItem End If If useDataSource Then ' Save the number of items contained for use in round trips. If Not (dataSource Is Nothing) Then ViewState("ItemCount") = count Else ViewState("ItemCount") = -1 End If End If End Sub Protected Overrides Function CreateControlStyle() As Style ' Since the TemplatedList control renders an HTML table, ' an instance of the TableStyle class is used as the control style. Dim style As New TableStyle(ViewState) ' Set up default initial state. style.CellSpacing = 0 Return style End Function Private Function CreateItem(table As Table, itemIndex As Integer, itemType As ListItemType, dataBind As Boolean, dataItem As Object) As TemplatedListItem Dim item As New TemplatedListItem(itemIndex, itemType) Dim e As New TemplatedListItemEventArgs(item) If Not (_itemTemplate Is Nothing) Then _itemTemplate.InstantiateIn(item.Cells(0)) End If If dataBind Then item.DataItem = dataItem End If OnItemCreated(e) table.Rows.Add(item) If dataBind Then item.DataBind() OnItemDataBound(e) item.DataItem = Nothing End If Return item End Function Public Overrides Sub DataBind() ' Controls with a data-source property perform their custom data binding ' by overriding DataBind. ' Evaluate any data-binding expressions on the control itself. MyBase.OnDataBinding(EventArgs.Empty) ' Reset the control state. Controls.Clear() ClearChildViewState() ' Create the control hierarchy using the data source. CreateControlHierarchy(True) ChildControlsCreated = True TrackViewState() End Sub Protected Overrides Sub LoadViewState(savedState As Object) ' Customized state management to handle saving state of contained objects. If Not (savedState Is Nothing) Then Dim myState As Object() = CType(savedState, Object()) If Not (myState(0) Is Nothing) Then MyBase.LoadViewState(myState(0)) End If If Not (myState(1) Is Nothing) Then CType(ItemStyle, IStateManager).LoadViewState(myState(1)) End If If Not (myState(2) Is Nothing) Then CType(SelectedItemStyle, IStateManager).LoadViewState(myState(2)) End If If Not (myState(3) Is Nothing) Then CType(AlternatingItemStyle, IStateManager).LoadViewState(myState(3)) End If End If End Sub Protected Overrides Function OnBubbleEvent(source As Object, e As EventArgs) As Boolean ' Handle events raised by children by overriding OnBubbleEvent. Dim handled As Boolean = False If TypeOf e Is TemplatedListCommandEventArgs Then Dim ce As TemplatedListCommandEventArgs = CType(e, TemplatedListCommandEventArgs) OnItemCommand(ce) handled = True If String.Compare(ce.CommandName, "Select", True) = 0 Then SelectedIndex = ce.Item.ItemIndex OnSelectedIndexChanged(EventArgs.Empty) End If End If Return handled End Function Private Sub PrepareControlHierarchy() If HasControls() = False Then Return End If Debug.Assert(TypeOf Controls(0) Is Table) Dim table As Table = CType(Controls(0), Table) table.CopyBaseAttributes(Me) If ControlStyleCreated Then table.ApplyStyle(ControlStyle) End If ' The composite alternating item style; do just one ' merge style on the actual item. Dim altItemStyle As Style = Nothing If Not (_alternatingItemStyle Is Nothing) Then altItemStyle = New TableItemStyle() altItemStyle.CopyFrom(_itemStyle) altItemStyle.CopyFrom(_alternatingItemStyle) Else altItemStyle = _itemStyle End If Dim rowCount As Integer = table.Rows.Count Dim i As Integer For i = 0 To rowCount - 1 Dim item As TemplatedListItem = CType(table.Rows(i), TemplatedListItem) Dim compositeStyle As Style = Nothing Select Case item.ItemType Case ListItemType.Item compositeStyle = _itemStyle Case ListItemType.AlternatingItem compositeStyle = altItemStyle Case ListItemType.SelectedItem If (True) Then compositeStyle = New TableItemStyle() If item.ItemIndex Mod 2 <> 0 Then compositeStyle.CopyFrom(altItemStyle) Else compositeStyle.CopyFrom(_itemStyle) End If compositeStyle.CopyFrom(_selectedItemStyle) End If End Select If Not (compositeStyle Is Nothing) Then item.MergeStyle(compositeStyle) End If Next i End Sub Protected Overrides Sub Render(writer As HtmlTextWriter) ' Apply styles to the control hierarchy ' and then render it out. ' Apply styles during render phase, so the user can change styles ' after calling DataBind without the property changes ending ' up in view state. PrepareControlHierarchy() RenderContents(writer) End Sub Protected Overrides Function SaveViewState() As Object ' Customize state management to handle saving state of contained objects such as styles. Dim baseState As Object = MyBase.SaveViewState() Dim itemStyleState As Object Dim selectedItemStyleState As Object Dim alternatingItemStyleState As Object If Not (_itemStyle Is Nothing) Then itemStyleState = CType(_itemStyle, IStateManager).SaveViewState() Else itemStyleState = Nothing End If If Not (_selectedItemStyle Is Nothing) Then selectedItemStyleState = CType(_selectedItemStyle, IStateManager).SaveViewState() Else selectedItemStyleState = Nothing End If If Not (_alternatingItemStyle Is Nothing) Then alternatingItemStyleState = CType(_alternatingItemStyle, IStateManager).SaveViewState() Else alternatingItemStyleState = Nothing End If Dim myState(4) As Object myState(0) = baseState myState(1) = itemStyleState myState(2) = selectedItemStyleState myState(3) = alternatingItemStyleState Return myState End Function Protected Overrides Sub TrackViewState() ' Customize state management to handle saving state of contained objects such as styles. MyBase.TrackViewState() If Not (_itemStyle Is Nothing) Then CType(_itemStyle, IStateManager).TrackViewState() End If If Not (_selectedItemStyle Is Nothing) Then CType(_selectedItemStyle, IStateManager).TrackViewState() End If If Not (_alternatingItemStyle Is Nothing) Then CType(_alternatingItemStyle, IStateManager).TrackViewState() End If End Sub #End Region End Class Public Class TemplatedListItem Inherits TableRow Implements INamingContainer Private _itemIndex As Integer Private _itemType As ListItemType Private _dataItem As Object Public Sub New(itemIndex As Integer, itemType As ListItemType) Me._itemIndex = itemIndex Me._itemType = itemType Cells.Add(New TableCell()) End Sub Public Overridable Property DataItem() As Object Get Return _dataItem End Get Set _dataItem = value End Set End Property Public Overridable ReadOnly Property ItemIndex() As Integer Get Return _itemIndex End Get End Property Public Overridable ReadOnly Property ItemType() As ListItemType Get Return _itemType End Get End Property Protected Overrides Function OnBubbleEvent(source As Object, e As EventArgs) As Boolean If TypeOf e Is CommandEventArgs Then ' Add the information about Item to CommandEvent. Dim args As New TemplatedListCommandEventArgs(Me, source, CType(e, CommandEventArgs)) RaiseBubbleEvent(Me, args) Return True End If Return False End Function Friend Sub SetItemType(itemType As ListItemType) Me._itemType = itemType End Sub End Class NotInheritable Public Class TemplatedListCommandEventArgs Inherits CommandEventArgs Private _item As TemplatedListItem Private _commandSource As Object Public Sub New(item As TemplatedListItem, commandSource As Object, originalArgs As CommandEventArgs) MyBase.New(originalArgs) Me._item = item Me._commandSource = commandSource End Sub Public ReadOnly Property Item() As TemplatedListItem Get Return _item End Get End Property Public ReadOnly Property CommandSource() As Object Get Return _commandSource End Get End Property End Class Public Delegate Sub TemplatedListCommandEventHandler(source As Object, e As TemplatedListCommandEventArgs) NotInheritable Public Class TemplatedListItemEventArgs Inherits EventArgs Private _item As TemplatedListItem Public Sub New(item As TemplatedListItem) Me._item = item End Sub Public ReadOnly Property Item() As TemplatedListItem Get Return _item End Get End Property End Class Public Delegate Sub TemplatedListItemEventHandler(sender As Object, e As TemplatedListItemEventArgs) NotInheritable Friend Class DummyDataSource Implements ICollection Private dataItemCount As Integer Public Sub New(dataItemCount As Integer) Me.dataItemCount = dataItemCount End Sub Public ReadOnly Property Count() As Integer Implements ICollection.Count Get Return dataItemCount End Get End Property Public ReadOnly Property IsReadOnly() As Boolean Get Return False End Get End Property Public ReadOnly Property IsSynchronized() As Boolean Implements ICollection.IsSynchronized Get Return False End Get End Property Public ReadOnly Property SyncRoot() As Object Implements ICollection.SyncRoot Get Return Me End Get End Property Public Sub CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo Dim e As IEnumerator While e.MoveNext() array.SetValue(e.Current, index) index += 1 End While End Sub Public Function GetEnumerator() As IEnumerator Implements ICollection.GetEnumerator Return New DummyDataSourceEnumerator(dataItemCount) End Function Private Class DummyDataSourceEnumerator Implements IEnumerator Private count As Integer Private index As Integer Public Sub New(count As Integer) Me.count = count Me.index = - 1 End Sub Public ReadOnly Property Current() As Object Implements IEnumerator.Current Get Return Nothing End Get End Property Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext index += 1 Return index < count End Function Public Sub Reset() Implements IEnumerator.Reset Me.index = - 1 End Sub End Class End Class End Namespace
Using the Data-Bound Control on a Page
The following sample shows an ASP.NET page that uses the TemplatedList control.
<%@ Page language="C#" %> <%@ Register TagPrefix="custom" Namespace="CustomControls" Assembly="CustomControls"%> <html> <head> <title>Data-Bound Control Sample</title> </head> <body> <form runat="server"> <h3>TemplatedList Control Sample</h3> <hr> <custom:TemplatedList runat="server" id="myList" Font-Name="Verdana" Font-Size="16pt" BorderColor="Gray" BorderWidth="1px" CellSpacing="0" CellPadding="2" GridLines="Both" onItemCreated="MyList_ItemCreated" onSelectedIndexChanged="MyList_SelectedIndexChanged"> <ItemStyle ForeColor="Black" BackColor="#EEEEEE"/> <AlternatingItemStyle BackColor="#DCDCDC"/> <SelectedItemStyle ForeColor="White" BackColor="#000084"/> <ItemTemplate> <asp:Button runat="server" id="selectButton" CommandName="Select" Text="Select" ForeColor="Blue"></asp:Button> <asp:Label runat="server" Text='<%# Container.DataItem %>'/> </ItemTemplate> </custom:TemplatedList> <hr> <asp:Label runat="server" id="infoLabel"></asp:Label> <hr> </form> <script runat="server"> private int selectedIndex = -1; private void LoadData() { ArrayList data = new ArrayList(); for (int i = 0; i < 10; i++) { data.Add("Item " + i); } myList.DataSource = data; myList.DataBind(); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!IsPostBack) { LoadData(); } } protected void MyList_ItemCreated(object sender, TemplatedListItemEventArgs e) { if (e.Item.ItemType == ListItemType.SelectedItem) { selectedIndex = e.Item.ItemIndex; Button selectButton = (Button)e.Item.FindControl("selectButton"); selectButton.Enabled = false; } } protected void MyList_SelectedIndexChanged(object sender, EventArgs e) { if (selectedIndex != -1) { Control item = myList.Controls[0].Controls[selectedIndex]; Button selectButton = (Button)item.FindControl("selectButton"); selectButton.Enabled = true; } selectedIndex = myList.SelectedIndex; infoLabel.Text = "SelectedIndex: " + selectedIndex; if (selectedIndex != -1) { Control item = myList.Controls[0].Controls[selectedIndex]; Button selectButton = (Button)item.FindControl("selectButton"); selectButton.Enabled = false; } } </script> </body> </html> [Visual Basic] <%@ Page language="VB"%> <%@ Register TagPrefix="custom" Namespace="CustomControls" Assembly="CustomControls"%> <html> <head> <title>Data-Bound Control Sample</title> </head> <body> <form runat="server"> <h3>TemplatedList Control Sample</h3> <hr> <custom:TemplatedList runat="server" id="myList" Font-Name="Verdana" Font-Size="16pt" BorderColor="Gray" BorderWidth="1px" CellSpacing="0" CellPadding="2" GridLines="Both" onItemCreated="MyList_ItemCreated" onSelectedIndexChanged="MyList_SelectedIndexChanged"> <ItemStyle ForeColor="Black" BackColor="#EEEEEE"/> <AlternatingItemStyle BackColor="#DCDCDC"/> <SelectedItemStyle ForeColor="White" BackColor="#000084"/> <ItemTemplate> <asp:Button runat="server" id="selectButton" CommandName="Select" Text="Select" ForeColor="Blue"></asp:Button> <asp:Label runat="server" Text='<%# Container.DataItem %>'/> </ItemTemplate> </custom:TemplatedList> <hr> <asp:Label runat="server" id="infoLabel"></asp:Label> <hr> </form> <script runat="server"> Private selectedIndex As Integer = - 1 Private Sub LoadData() Dim data As New ArrayList() Dim i As Integer For i = 0 To 9 data.Add("Item " & i) Next i myList.DataSource = data myList.DataBind() End Sub Protected Overrides Sub OnLoad(e As EventArgs) MyBase.OnLoad(e) If Not IsPostBack Then LoadData() End If End Sub Protected Sub MyList_ItemCreated(sender As Object, e As TemplatedListItemEventArgs) If e.Item.ItemType = ListItemType.SelectedItem Then selectedIndex = e.Item.ItemIndex Dim selectButton As Button = CType(e.Item.FindControl("selectButton"), Button) selectButton.Enabled = False End If End Sub Protected Sub MyList_SelectedIndexChanged(sender As Object, e As EventArgs) If selectedIndex <> - 1 Then Dim item As Control = myList.Controls(0).Controls(selectedIndex) Dim selectButton As Button = CType(item.FindControl("selectButton"), Button) selectButton.Enabled = True End If selectedIndex = myList.SelectedIndex infoLabel.Text = "SelectedIndex: " + selectedIndex If selectedIndex <> - 1 Then Dim item As Control = myList.Controls(0).Controls(selectedIndex) Dim selectButton As Button = CType(item.FindControl("selectButton"), Button) selectButton.Enabled = False End If End Sub </script> </body> </html>