本篇将开始介绍如自定义ASP.NET数据绑定控件,这里感谢很多人的支持,有你们的支持很高兴.
这里首先需要大家熟悉ASP.NET模板控件的使用,还有自定义模板控件.因为数据绑定控件多是基于模板控件的.
ASP.NET数据绑定控件一.回顾
如果你使用过ASP.NET内置的数据控件(如DataList,Repeater),你一定会这么做
1.设置数据源 DataSource属性
2.调用数据绑定 DataBind方法
3.在控件的不同模板内使用绑定语法显示数据
这三步应该是必须要做的
其他更多的
你可能需要对绑定的数据进行统一的一些操作(如时间格式化),或者对数据的某一项进行操作(对某一项进行格式化),或者需要触发模板控件内的一些事件(如databound事件).
根据上面的一些需求,我们需要这样做
1.对绑定的数据进行统一的一些操作: 为数据绑定控件定义Item项(表示列表的一条数据, 如Repeater的RepeaterItem)
2.对数据的某一项进行操作: 因为定义了Item项,那你肯定需要一个ItemCollection集合,其可以方便的为你检索数据
3.因为定义了RepeaterItem,原先的EventArgs和CommandEventArgs已经无法满足需求,我们需要自定义委托及其一个为控件提供数据的的ItemEventArgs
上面三点有些并非必须定义,如第2点,还需要根据具体需求来定.但一个完成的控件是需要的.
ASP.NET数据绑定控件二.为数据控件做好准备
这次的demo为不完整的Datalist控件,来源还是MSDN的例子,我们命名为TemplatedList,此控件未定义ItemCollection集合
好了,根据上面的分析我们先为TemplatedList提供项和委托及为事件提供数据的几个EventArgs,请看下面类图
1.TemplatedListCommandEventArgs为Command事件提供数据
2.TemplatedListItemEventArgs为一般项提供数据
3.TemplatedListItem表示TemplatedList的项
ASP.NET数据绑定控件三.编写TemplatedList
1.TemplatedList主要功能简介
提供一个ItemTemplate模板属性,提供三种不同项样式,ItemCommand 事件冒泡事件及4个事件
2.实现主要步骤
以下为必须
(1)控件必须实现 System.Web.UI.INamingContainer 接口
(2)定义至少一个模板属性
(3)定义DataSource数据源属性
(4)定义控件项DataItem,即模板的一个容器
(5)重写DataBind 方法及复合控件相关方法(模板控件为特殊的复合控件)
当然还有其他额外的属性,样式,事件
3.具体实现
下面我们来具体看实现方法
(1)定义控件成员属性
- #region 静态变量
- 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 成员变量
- private IEnumerable dataSource;
- private TableItemStyle itemStyle;
- private TableItemStyle alternatingItemStyle;
- private TableItemStyle selectedItemStyle;
- private ITemplate itemTemplate;
- #endregion
- 控件属性#region 控件属性
- [
- Category("Style"),
- Description("交替项样式"),
- 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;
- }
- }
- [
- Category("Style"),
- Description("一般项样式"),
- 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;
- }
- }
- [
- Category("Style"),
- Description("选中项样式"),
- 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;
- }
- }
- [
- 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("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;
- }
- }
- [
- Bindable(true),
- Category("Data"),
- DefaultValue(null),
- Description("数据源"),
- DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
- ]
- public IEnumerable DataSource
- {
- get
- {
- return dataSource;
- }
- set
- {
- dataSource = value;
- }
- }
- [
- Browsable(false),
- DefaultValue(null),
- Description("项模板"),
- PersistenceMode(PersistenceMode.InnerProperty),
- TemplateContainer(typeof(TemplatedListItem))
- ]
- public virtual ITemplate ItemTemplate
- {
- get
- {
- return itemTemplate;
- }
- set
- {
- itemTemplate = value;
- }
- }
- [
- Bindable(true),
- DefaultValue(-1),
- Description("选中项索引,默认为-1")
- ]
- 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);
- }
- }
- }
- }
- #endregion
成员如下(可以看上面类图)
1.三个项样式和三个样式属性
2.公开DataSource数据源属性,一个模板属性
3.SelectedIndex索引属性
前面的相信大家都很容易明白,其中的三个项样式我们需要为其重写视图状态管理,不熟悉可以看以前的随笔,这里不再重复.
SelectedIndex属性比较复杂,这里重点介绍此属性
SelectedIndex索引属性默认为-1,
我给出了注释,在赋值前先记录下了上次的选中项,为恢复样式而做准备
- //获取上次选中项
- int oldSelectedIndex = SelectedIndex;
- ViewState["SelectedIndex"] = value;
当第一次更改SelectedIndex属性时只执行下列代码(将此项标记为选中项),因为初始化时的没有oldSelectedIndex,不需要恢复样式
- //第一次执行此项,并一直执行
- if ((value != -1) && (table.Rows.Count > value))
- {
- item = (TemplatedListItem)table.Rows[value];
- item.SetItemType(ListItemType.SelectedItem);
- }
再次执行时,恢复oldSelectedIndex选中项样式
- //第一次选中项不执行
- 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);
- }
- }
相信这样的解释你会明白
(2)定义控件成员事件
我们可以用上刚才我们声明的委托了,即然你定义了这么多事件,就该为其安排触发的先后.所以这个要特别注意,等下会再次提到.
- #region 事件
- 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
(3)关键实现
我们为控件提供了这么多东西,剩下的事情就是要真正去实现功能了
1.重写DataBind方法
当控件绑定数据时首先会执行此方法触发DataBinding事件
- //控件执行绑定时执行
- public override void DataBind()
- {
- base.OnDataBinding(EventArgs.Empty);
- //移除控件
- Controls.Clear();
- //清除视图状态信息
- ClearChildViewState();
- //创建一个带或不带指定数据源的控件层次结构
- CreateControlHierarchy(true);
- ChildControlsCreated = true;
- TrackViewState();
- }
2.CreateControlHierarchy方法
- /**//// <summary>
- /// 创建一个带或不带指定数据源的控件层次结构
- /// </summary>
- /// <param name="useDataSource">指示是否要使用指定的数据源</param>
- //注意:当第二次执行数据绑定时,会执行两遍
- 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)
- {
- //保存项数量
- ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
- }
- }
- //创建项
- 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;
- }
CreateItem方法辅助用于创建项模板,此处注意事件触发顺序,上面已经提到过
此方法根据项索引创建控件中不同的Item项 ,ViewState["ItemCount"]表示项的数量,第一次触发时或者重新执行DataBind方法时方法参数为true,并在初始化以后(回发期间)CreateChildControls方法会调用此方法,其参数为false
数据源不再是实际的数据源,而是新定义的DummyDataSource,其主要实现了一个迭代
- 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;
- }
- }
- }
原因很明显,为了减少对数据源的访问,所以我们平时操作数据的时候,必须重新执行DataBind方法,原因就在此
好了,到了这里差不多主要的事情我们已经完成.接着把剩下的也完成
3.呈现
又到了Render方法这里了
此方法体只要执行了PrepareControlHierarchy方法,不同的方法做不同的事情,CreateControlHierarchy方法根据索引值指定了不同的项,PrepareControlHierarchy则为不同项呈现不同的样式效果
- //为不同类型项加载样式
- 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);
- }
终于差不多了,经过这么多步骤,我们终于完成了,让我们来使用控件,看一下效果
又完成一个并不完美的控件,本来还该继续下去的,怕篇幅太大,到这里还没结束,只是刚开始,下次我们继续
ASP.NET数据绑定控件的基本情况就向你介绍到这里,希望对你了解ASP.NET数据绑定控件有所帮助。
【编辑推荐】