• 自定义有模板功能的控件



    任何控件都可以包含子控件。一个控件支持模板,其实就是在往它里面加子控件。模板是用什么形式在控件内公开的呢?想一想,我们用一个一般的控件都可以直接用标记来指定属性,而模板也是使用标记来操作。使用模板的时候,就是用标记的方法给模板赋值,根据“任何控件都可以包含子控件”,所以模板里也可以再包含控件。因此,模板在控件定义中就表现为属性。我们的标记都是用xml形式呈现的,因此,使用模板实际上就是给控件的某个属性赋于xml标记这样的值。
    上面的话是否可以看懂呢?看下面的这段标记。
    <cc1:TemplateControl id="tem" runat="server">
    <ITEMTEMPLATE>
    <asp:Button id="Button1" Text="<%# Container.Text%>" Runat="server">
    </asp:Button>
    <asp:Label ID="Label1" Text="<%# Container.Text%>" Runat="server"></asp:Label><br>
    <div>hello world !</div>
    </ITEMTEMPLATE>
    </cc1:TemplateControl>
    这是一个支持模板及数据绑定的自定义控件。它是这样完成的。
    ITEMTEMPLATE是一个ITemplate类型的属性,只要对外公开了此属性,就可以使控件支持模板。
    //模板
    private ITemplate itemTemplate;


    public ITemplate ItemTemplate
    {
    get{ return this.itemTemplate; }
    set{ this.itemTemplate=value; }
    }

    设置好属性后,还需要给控件加一个类级别的[ParseChildren(true)]特性,用来指明将控件内的XML标记元素视为属性处理,这样就可以在<ITEMTEMPLATE>标签内定义自己的模板。
    作完这些后,下一步需要做的就是要将模板做为子控件加入到控件中。
    protected override void CreateChildControls()
    {
    if(this.itemTemplate!=null)
    {
    //当由类实现时,定义子控件和模板所属的 Control 对象。然后在内联模板中定义这些子控件。
    //这里把容器控件指定为代表控件本身的this
    this.itemTemplate.InstantiateIn(this);
    }
    else
    this.Controls.Add(new LiteralControl("no itemtemplate!"));
    base.CreateChildControls();
    }
    通过重写CreateChildControls方法来完成。
    只有调用DataBind,才可能将数据绑定到子控件。因此我们还需要重写OnDataBinding
    protected override void OnDataBinding(EventArgs e)
    {
    //确定服务器控件是否包含子控件。如果不包含,则创建子控件。
    this.EnsureChildControls();
    base.OnDataBinding (e);
    }

    到这里,一个简单的支持模板的控件就算完成了,可以自己把上面的东西组合起来看看效果。
    我们需要模板一般都是要进行数据绑定的,接下来看看怎么实现。
    <%# Container.Text%>
    上面是一般的绑定表达式,可以看到,其实就是调用了一个对象的属性,其中Container用来表示乘放模板的容器,而Text正是该容器的属性。
    一般用TemplateContainer特性来指定容器的类型,像这样:
    [TemplateContainer(typeof(ContainerControl)),Browsable(false)]
    public ITemplate ItemTemplate
    { //... }
    ContainerControl就是一个容器控件,其定义如下
    public class ContainerControl : Control,INamingContainer
    {
    private string text;

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

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

    注意,这是一个控件,只是没有向页面呈现而已。实际上,它的作用就是用来乘放模板。这样,我们创建模板时就改成了这样子
    ContainerControl cc=new ContainerControl(text);
    this.itemTemplate.InstantiateIn(cc);
    this.Controls.Add(cc);

    下面是这个控件的完整的代码 :)
    using System;
    using System.Web.UI;
    using System.ComponentModel;

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

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

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

    //辅助进行绑定的属性
    private string text;

    public string Text
    {
    get{ return this.text; }
    set{ this.text=value; }
    }


    /// <summary>
    /// 创建子控件
    /// </summary>
    protected override void CreateChildControls()
    {
    if(this.itemTemplate!=null)
    {
    //使用得到的属性来创建容器控件
    ContainerControl cc=new ContainerControl(this.text);
    //将模板添加到专门的容器中
    this.itemTemplate.InstantiateIn(cc);
    //将容器加到父控件中
    this.Controls.Add(cc);
    }
    else
    this.Controls.Add(new LiteralControl("no itemtemplate!"));
    base.CreateChildControls();
    }

    /// <summary>
    /// 在这里进行检查并创建模板
    /// </summary>
    /// <param name="e"></param>
    protected override void OnDataBinding(EventArgs e)
    {
    //确定服务器控件是否包含子控件。如果不包含,则创建子控件。
    this.EnsureChildControls();
    base.OnDataBinding (e);
    }

    }

    }

    具体使用的页面,.aspx页上的标记就像我们一开始那样,.cs里的代码为
    if(!this.IsPostBack)
    {
    this.tem.Text="hello world !";
    this.tem.DataBind();
    }
    在这里,就算postback了,显示的页面还是没有改变,这主要是因为模板内的控件各自维持自己的状态,了解这一点很重要,因为接下来支持数据绑定时需要使用这一点。
    还有一点,如果把DataBind注释掉了,那Text就不会绑定到模板上。你可以实验一下把EnsureChildControls放到其他方法中(比如Render什么的),结果都是如此。

    接着上一篇,今天来做支持数据绑定的模板控件。
    使用支持数据绑定控件的一般模式为
    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控件!
    这里对状态维持有一个bug,使用该控件时如果不对postback处理时,当发生postback时就会发生
    private void Page_Load(object sender, System.EventArgs e)
    {
    this.tm.DataSource=new string[]{"one","two"};
    this.tm.DataBind();
    }
    这样,没有判断postback,虽然这不是一个好的程序,但这种情况确实会发生。
    具体表现为模板重复出现了两此,并且一开始出现的模板是没有内容的……这主要是因为页面发生回发后,在控件内部按照时间的先后调用CreateChildControls和OnDataBinding。这样模板就被重复创建了两次,由于CreateChildControls内模板创建的初衷是赋给他们空值,然后让viewstate来填充。但由于OnDataBinding重新创建了模板的实例,使得需要的viewstate消失,所以就出现了空值的情况。解决的办法是在OnDataBinding中加一个Controls.Clear(),这样就可以把之前创建的没有被赋值的控件clear掉!
    protected override void OnDataBinding(EventArgs e)
    {
    //把之前创建的没有被赋值的控件clear掉!
    this.Controls.Clear();
    this.createTemplate();
    base.OnDataBinding (e);
    }
  • 相关阅读:
    关于面试的有意义问题
    Web测试方法总结
    alwayson05-安装sql server
    alwayson03-安装故障转移
    always on
    alwayson02-各个节点加入域
    alwayson01-搭建域环境
    undo log和redo log
    mysql事务隔离级别
    Ajax
  • 原文地址:https://www.cnblogs.com/huqingyu/p/72349.html
Copyright © 2020-2023  润新知