本篇将开始介绍如自定义数据绑定控件,这里感谢很多人的支持,有你们的支持很高兴。这里首先需要大家熟悉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);
}

  终于差不多了,经过这么多步骤,我们终于完成了,让我们来使用控件,看一下效果
  又完成一个并不完美的控件,本来还该继续下去的,怕篇幅太大,到这里还没结束,只是刚开始,下次我们继续

上一篇:asp.net控件开发基础(16)

下一篇:asp.net控件开发基础(18)

一起谈.NET技术,asp.net控件开发基础(17)相关推荐

  1. 一起谈.NET技术,asp.net控件开发基础(18)

    本篇继续上篇的讨论,可能大家已经在使用asp.net2.0了,DataSource属性不再使用,而是跟数据源控件搭配使用.现在讨论的绑定技术都是基于1.1版本,先熟悉一下,本质上是一样的,这样一步步的 ...

  2. asp.net控件开发基础系列

    本系列文章示例源码下载.各位如遇问题,请多查msdn,多利用网络.本人可能没时间一一回复,谢谢你们的支持,希望看到此文的人都能学好控件开发 http://www.cnblogs.com/Clingin ...

  3. 一起谈.NET技术,asp.net控件开发基础(13)

    1.减轻服务器压力,增加用户体验 服务器功能是强大的,客户端脚本一点也不弱,现在的ajax技术和Atlas技术就是最好的证明,我们总是期待UI有一个好的效果,flash动画给我们带来了很酷的效果,我们 ...

  4. 一起谈.NET技术,asp.net控件开发基础(20)

    上面我们讨论了数据绑定控件的做法,但都未涉及到asp.net2.0中数据源控件的用法,让用惯了数据源控件的人可能感觉不适应.这次我们就开始讨论在asp.net2.0中,我们该如何重新定义数据绑定控件. ...

  5. 一起谈.NET技术,asp.net控件开发基础(9)

    写第五篇的时候,我一步步的加上元数据(特性),使得设计时效果更加好,如对复杂属性应用以下特性,使属性浏览器支持扩展/折叠效果,使你更加容易编辑子属性,但接着我又遇到了问题,所以必须去解决 1.认识默认 ...

  6. asp.net控件开发基础(2)

    上一篇:http://www.cnblogs.com/Clingingboy/archive/2006/07/30/463471.html 或许大家还对为何要重写Render方法存有疑惑,希望大家看看 ...

  7. asp.net控件开发基础十四

    http://www.cnblogs.com/Clingingboy/archive/2006/09/29/514722.html      上一篇讨论了为服务器控件添加客户端功能,这一篇我们所要讲的 ...

  8. asp.net控件开发基础(21)

    示例代码 上篇介绍了在asp.net2.0版本下面如何简单的定义数据绑定控件.虽然DataBoundControl为我们提供了便利,我们以后可以从此类开始编写数据绑定控件.但是在2.0版本未到来之前, ...

  9. asp.net控件开发基础(19)

    上两篇讨论了基本数据绑定控件的实现步骤,基本上我们按着步骤来就可以做出简单的数据绑定控件了.过年前在看DataGrid的实现,本来想写这个的,但2.0出了GridView了,再说表格控件实现比较复杂, ...

最新文章

  1. Nginx(一)------简介与安装
  2. ET001 不可不掌握的 Logstash 使用技巧
  3. 2020 云原生技术 7 大领域趋势全预测
  4. 前端也要会的数据结构 (不定期更新篇)
  5. Linux命令-自动挂载文件/etc/fstab功能详解
  6. 基于 Intel 的 Mac,如何使用机构恢复密钥的技巧
  7. 计算机学科技术前沿:INFOCOM和SIGCOMM会议信息
  8. 带约束的遗传算法代码优化
  9. 十分钟读完《金字塔原理》
  10. windows10安装NVIDIA显卡驱动+cuda10.0教程
  11. 云计算网络,在数据中心之中主要有哪些优势?
  12. light-bot小游戏
  13. 微信小程序的wx:for和vue的v-for
  14. 给LINUX安装JDK
  15. anaconda中的python3环境与ros公用时的问题
  16. html电商app小图标素材,20套购物网站专用的图标素材包
  17. 教育技术和计算机专业有什么区别,计算机教育与教育技术学有什么不同吗?
  18. 德国php空间申请,德国host1free提供免费10G无广告可绑米PHP空间申请教程
  19. 和Stand-up再次相遇
  20. python--健康体检

热门文章

  1. Netbeans Platform的Lookup -- 边学边记
  2. 在Windows Server 2008中安装Exchange Server 2007 SP1
  3. python脚本-记录Python脚本的运行日志的方法
  4. iOS开发 mac证书无法展开
  5. 使用AIDL实现进程间的通信之复杂类型传递
  6. 监听屏幕解锁和判断屏幕状态
  7. Android--Pin流程,飞行模式相关流程总结【工作日记一】
  8. 几米的一段话,献给所有小郁闷的人
  9. Django例子-出版社
  10. [INS-32025] 所选安装与指定 Oracle 主目录中已安装的软件冲突。