2004-10-21+ 自定义控件(支持模板+数据绑定)

接着上一篇,今天来做支持数据绑定的模板控件。
使用支持数据绑定控件的一般模式为
private void Page_Load(object sender, System.EventArgs e)
{
if(!this.IsPostBack)
{
this.tm.DataSource=new string[]{"one","two"};
this.tm.DataBind();
}
}

先指定数据源,当确认数据源正确后调用DataBind方法进行数据绑定。然后就开始调用控件自己的DataBinding事件的处理程序,一般是OnDataBinding,在这个方法里对数据源进行操作。
前面已经有文章分析了数据源,可以想到,和一般的控件不同的是,支持模板的控件在进行数据绑定时需要多做一步,除了要把数据从数据源取出,还要把这些数据绑到模板上。
还有,数据源一般都是在初次加载页面时用到,回发后就不再提供数据源,而是依靠viewstate来维持。因此“把数据绑到模板”这一步操作要放到OnDataBinding进行(因为DataBind只被使用一次),而CreateChildControls则要做一些维持状态的工作。
先要做的,是准备一个接收数据源的属性。
然后要有一个“把数据绑到模板”的方法,就像下面这个
private void createTemplate()
{
if(this.itemTemplate!=null)
{
if(this.dataSource!=null)
{
//计算创建了多少子模板
int itemCount=1;
//只是为了方便而做的假设dataSource是string[]
foreach(string text in (string[])this.dataSource)
{
ContainerControl cc=new ContainerControl(text);
this.itemTemplate.InstantiateIn(cc);
this.Controls.Add(cc);
itemCount++;
}
//保存数量到ViewState
this.ViewState["itemCount"]=itemCount;
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!1"));
this.ChildControlsCreated=true;
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!"));
}

在上面的方法中,使用遍历数据源的方法来循环添加模板容器。不明白可以去看上一篇笔记。
viewstate可以维持模板内控件的状态,但是它却不负责创建控件,因此我们在这里保存了模板的数量itemCount,然后在CreateChildControls中创建itemCount个空模板,这样viewstate就可以给这些空模板附加上状态。
protected override void CreateChildControls()
{
if(this.itemTemplate!=null)
{
int itemCount=(int)this.ViewState["itemCount"];
for(int i=1;i<itemCount;i++)
{
ContainerControl cc=new ContainerControl(string.Empty);
this.itemTemplate.InstantiateIn(cc);
this.Controls.Add(cc);
}
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!"));
base.CreateChildControls();
}


然后就成了,下面是全部的代码。

using System;
using System.Web.UI;
using System.ComponentModel;
using System.Collections;
using System.Data;

namespace CC
{
/// <summary>
/// 模板练习
/// </summary>

//分析控件内的xml元素标记
[ParseChildren(true)]
public class TemplateControl : Control,INamingContainer
{
//模板
private ITemplate itemTemplate;

//指定存放模板的容器控件
[TemplateContainer(typeof(ContainerControl)),Browsable(false)]
public ITemplate ItemTemplate
{
get{ return this.itemTemplate; }
set{ this.itemTemplate=value; }
}

//数据源
private object dataSource;

[Browsable(false)]
public object DataSource
{
get{ return this.dataSource; }
set{ this.dataSource=value; }
}

/// <summary>
/// 创建子控件,在这里创建的只是空壳,其内容根据ViewState来填充
/// </summary>
protected override void CreateChildControls()
{
if(this.itemTemplate!=null)
{
int itemCount=(int)this.ViewState["itemCount"];
for(int i=1;i<itemCount;i++)
{
ContainerControl cc=new ContainerControl(string.Empty);
//将模板添加到专门的容器中
this.itemTemplate.InstantiateIn(cc);
//将容器加到父控件中
this.Controls.Add(cc);
}
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!"));
base.CreateChildControls();
}

/// <summary>
/// 当用户端调用DataBind,则表示准备好了数据源,此时可以开始创建子模板
/// </summary>
/// <param name="e"></param>
protected override void OnDataBinding(EventArgs e)
{
//确定服务器控件是否包含子控件。如果不包含,则创建子控件。
//this.EnsureChildControls();
this.createTemplate();
base.OnDataBinding (e);
}

//手动创建子模板
private void createTemplate()
{
if(this.itemTemplate!=null)
{
//当由类实现时,定义子控件和模板所属的 Control 对象。然后在内联模板中定义这些子控件。
//this.itemTemplate.InstantiateIn(this);
if(this.dataSource!=null)
{
//计算创建了多少子模板
int itemCount=1;
//只是为了方便而做的假设dataSource是string[]
foreach(string text in (string[])this.dataSource)
{
ContainerControl cc=new ContainerControl(text);
//将模板添加到专门的容器中
this.itemTemplate.InstantiateIn(cc);
//将容器加到父控件中
this.Controls.Add(cc);
itemCount++;
}
//保存数量到ViewState
this.ViewState["itemCount"]=itemCount;
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!1"));
//确认子模板创建完毕
this.ChildControlsCreated=true;
}
else
this.Controls.Add(new LiteralControl("no itemtemplate!"));
}

}

/// <summary>
/// 容器控件,用来存放模板
/// </summary>
public class ContainerControl : Control,INamingContainer
{
private string text;

public string Text
{
get{ return this.text; }
}

public ContainerControl(string text)
{
this.text=text;
}
}

}

完成了这个,接下来我们就可以自己写一个Repeater控件!

2004-10-21补充<-----------------------------------------------

这里对状态维持有一个bug,使用该控件时如果不对postback处理时,当发生postback时就会发生
private void Page_Load(object sender, System.EventArgs e)
{
this.tm.DataSource=new string[]{"one","two"};
this.tm.DataBind();
}

这样,没有判断postback,虽然这不是一个好的程序,但这种情况确实会发生。
具体表现为模板重复出现了两此,并且一开始出现的模板是没有内容的……这主要是因为页面发生回发后,在控件内部按照时间的先后调用CreateChildControlsOnDataBinding。这样模板就被重复创建了两次,由于CreateChildControls内模板创建的初衷是赋给他们空值,然后让viewstate来填充。但由于OnDataBinding重新创建了模板的实例,使得需要的viewstate消失,所以就出现了空值的情况。解决的办法是在OnDataBinding中加一个Controls.Clear(),这样就可以把之前创建的没有被赋值的控件clear掉!
protected override void OnDataBinding(EventArgs e)
{
//把之前创建的没有被赋值的控件clear掉!
this.Controls.Clear();
this.createTemplate();
base.OnDataBinding (e);
}

posted on 2006-07-03 18:41  Notus|南色的风  阅读(740)  评论(0编辑  收藏  举报