您的位置:首页 > 编程语言 > ASP

asp.net控件开发基础(17) --------初识数据绑定控件

2007-03-21 21:58 781 查看
本篇将开始介绍如自定义数据绑定控件,这里感谢很多人的支持,有你们的支持很高兴.

这里首先需要大家熟悉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点,还需要根据具体需求来定.但一个完成的控件是需要的.

二.为数据控件做好准备

这次的demo为不完整的Datalist控件,来源还是MSDN的例子,我们命名为TemplatedList,此控件未定义ItemCollection集合

好了,根据上面的分析我们先为TemplatedList提供项和委托及为事件提供数据的几个EventArgs,请看下面类图



1.TemplatedListCommandEventArgs为Command事件提供数据

2.TemplatedListItemEventArgs为一般项提供数据

3.TemplatedListItem表示TemplatedList的项

三.编写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);


}

终于差不多了,经过这么多步骤,我们终于完成了,让我们来使用控件,看一下效果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息