C#开发训练营

第4课:ASP.NET(4)——自定义控件

虽然ASP.NET Web项目中可以使用的Web控件很多,但在一些特殊情况下还是需要自定义一些控件,本节将讨论两种创建自定义控件的方法,分别是使用C#类和.ascx文件。

HTML5日期和时间控件(基于C#类)

HTML5标准中,input元素又增加了一些类型,如显示和输入日期的date属性;那么,在ASP.NET Web项目中,我们如何使用此属性创建构建自己的日期控件呢?

如下面的代码(/app_code/aspnet/ctr/CtrDate.cs),我们利用WebControl类作为基类创建CtrDate控件。

C#
using System;
using System.Collections.Specialized;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace acl.aspnet.ctr
{
    [ControlValueProperty("Value")]
    [ValidationProperty("Value")]
    public class CtrDate : WebControl, IPostBackDataHandler
    {
        public string Value
        {
            get
            {
                return (string)ViewState["Value"];
            }
            set
            {
                ViewState["Value"] = value;
            }
        }

        public virtual bool LoadPostData(string postDataKey,
           NameValueCollection postCollection)
        {
            string presentValue = Value;
            string postedValue = postCollection[postDataKey];
            if (presentValue == null || !presentValue.Equals(postedValue))
            {
                Value = postedValue;
                return true;
            }
            return false;
        }

        public virtual void RaisePostDataChangedEvent()
        {
            //
        }

        protected override void Render(HtmlTextWriter output)
        {
            output.Write("<input type=date id=" + UniqueID + " name=" + UniqueID
               + " value=" + Value + ">");
        }
    }
}

代码中,我们将CtrDate类定义在acl.aspnet.ctr命名空间,在注册控件时需要指定控件所在的命名空间,稍后可以看到如何在Web.config文件中注册控件。

这里,通过继承WebControl类,并实现 IPostBackDataHandler接口创建了CtrDate控件;其中实现了一些基础的控件成员,如Value属性等。

显示日期控件的关键在Render()方法,这里定义了页面中呈现控件的代码,其中定义了一个input元素,id和name属性定义为控件的唯一ID,type属性定义为date,value属性定义为Text属性。

设置type属性为date值的input元素值(value属性)时,需要使用标准的“YYYY-MM-DD”格式,即使用4位年份、2位月份和2位日期,当年份不足4位,月份和日期不足2位时需要使用前导0。在tDate类中定义了DateTime类型的扩展方法DateStr(),用于返回“YYYY-MM-DD”格式的年、月、日字符串,如下面的代码(/app_code/common/base/tDate.cs)。

C#
using System;

public static class tDate
{
    // 标准8位年、月、日格式
    public static string DateStr(this DateTime dt)
    {
        return string.Format("{0:d4}-{1:d2}-{2:d2}", dt.Year, dt.Month, dt.Day);
    }
    // 其它代码...
}

在ASP.NET Web项目中使用C#类定义的控件时,首先需要注册,如在Web.config配置文件的pages节点中添加如下代码。

XML
<pages controlRenderingCompatibilityVersion="4.0">
  <controls>
    <add tagPrefix="acl" namespace="acl.aspnet.ctr" />
  </controls>
</pages>

controls节点中使用add节点添加C#类定义的控件,这里只需要使用tagPrefix属性指定控件前缀,namespace属性指定控件所在的命名空间,页面中可以使用“<前缀>:<类名>”的格式定义控件。

接下来,我们通过/demo/ctr/CtrDateDemo.aspx页面测试CtrDate控件的使用,首先是HTML部分,如下面的代码。

HTML
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="CtrDateDemo.aspx.cs" Inherits="demo_ctr_CtrDateDemo" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <p>
          <acl:CtrDate ID="date1" runat="server" />  
        </p>
        <p>
          <acl:CtrDate ID="date2" runat="server" />
        </p>
        <p>
          <asp:TextBox ID="txt1" runat="server"></asp:TextBox>
        </p>
        <p>
          <asp:Button ID="btn1" runat="server" Text="测试一" OnClick="btn1_Click" />
          <asp:Button ID="btn2" runat="server" Text="测试二" OnClick="btn2_Click" />
        </p>
    </form>
</body>
</html>

代码中定义了一个CtrDate控件(date1)、一个文本控件(txt1)和一个按钮控件(btn1)。页面中,CtrDate控件显示为type属性是date的input控件,可以弹出日历选择日期,如图1(Google Chrome浏览器)。

图1

下面是页面的C#代码部分(/demo/ctr/CtrDateDemo.aspx.cs)。

C#
using System;

public partial class demo_ctr_CtrDateDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack == false)
        {
            date1.Value = DateTime.Now.DateStr();
        }
    }

    protected void btn1_Click(object sender, EventArgs e)
    {
        date2.Value = date1.Value;
    }

    protected void btn2_Click(object sender, EventArgs e)
    {
        txt1.Text = date1.Value;
    }
}

页面首次打开时,date1控件会显示系统当前日期,点击btn1控件时,date2控件中会显示date1中的日期,点击btn2按钮时会在txt1控件中会显示date2控件中的日期值,;如果CtrDate控件中没有选中日期,则Text属性会返回空字符串。页面操作效果如图2。

图2

HTML5标准中的input元素,当type属性设置为time时,可以定义为时间控件,这里封装为CtrTime控件,如下面的代码(/app_code/aspnet/ctr/CtrTime.cs)。

C#
using System;
using System.Collections.Specialized;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace acl.aspnet.ctr
{
    [ControlValueProperty("Value")]
    [ValidationProperty("Value")]
    public class CtrTime : WebControl, IPostBackDataHandler
    {
        public string Value
        {
            get
            {
                return (string)ViewState["Value"];
            }
            set
            {
                ViewState["Value"] = value;
            }
        }

        public virtual bool LoadPostData(string postDataKey,
           NameValueCollection postCollection)
        {
            string presentValue = Value;
            string postedValue = postCollection[postDataKey];
            if (presentValue == null || !presentValue.Equals(postedValue))
            {
                Value = postedValue;
                return true;
            }
            return false;
        }

        public virtual void RaisePostDataChangedEvent()
        {
            //
        }

        protected override void Render(HtmlTextWriter output)
        {
            output.Write("<input type=time id=" + UniqueID + " name=" + UniqueID
               + " value=" + Value + ">");
        }
    }
}

可以看到,CtrTime控件与CtrDate控件的创建非常相似,需要注意的是Render()方法中,呈现到客户端的input元素中的type属性应该设置为time。

实现应用中,CtrTime控件的Text属性数据格式为"hh:mm",即使用两位小时数据和两位分钟数据。Firefox浏览器中显示的内容包括上午或下午,而Google Chrome浏览器则直接使用24小时格式。

为方便获取标准格式的时间文本,在tDate类中定义了相应的扩展方法,如下面的代码(/app_code/common/base/tDate.cs)。

C#
public static string HourMinuteStr(this DateTime dt)
{
    return string.Format("{0:d2}:{1:d2}", dt.Hour, dt.Minute);
}

我们使用/demo/ctr/CtrTimeDemo.aspx页面测试CtrTime控件的使用,其中HTML代码部分如下。

HTML
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="CtrTimeDemo.aspx.cs" Inherits="demo_ctr_CtrTimeDemo" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <p>
          <acl:CtrTime ID="time1"  runat="server" />
        </p>
        <p>
          <acl:CtrTime ID="time2" runat="server" />
        </p>
        <p>
          <asp:TextBox ID="txt1" runat="server"></asp:TextBox>
        </p>
        <p>
          <asp:Button ID="btn1" Text="测试一" runat="server" OnClick="btn1_Click" />
          <asp:Button ID="btn2" Text="测试二" runat="server" OnClick="btn2_Click" />
        </p>
    </form>
</body>
</html>

页面的C#代码部分如下。

C#
using System;

public partial class demo_ctr_CtrTimeDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack == false)
        {
            time1.Value = DateTime.Now.HourMinuteStr();
        }
    }

    protected void btn1_Click(object sender, EventArgs e)
    {
        time2.Value = time1.Value;
    }

    protected void btn2_Click(object sender, EventArgs e)
    {
        txt1.Text = time1.Value;
    }
}

首次打开页面时,time1控件会显示系统当前时间(时、分),点击btn1按钮时,time2控件会显示time1中的时间,点击btn2按钮时,txt1文本框会显示时间的文本内容,执行效果如图3。

图3

页眉和页脚控件(基于.ascx)

另一种创建控件的方法是使用.ascx控件文件,类似于.aspx页面,.ascx控件也包括HTML部分和C#代码部分,当这两部分分别保存在不同的文件时,HTML部分使用.ascx文件,而C#代码文件则在.ascx文件名后添加.cs扩展名。

下面的代码(/lib/ctr/PageHeader.ascx)创建了页眉控件。

HTML
<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="PageHeader.ascx.cs" Inherits="lib_ctr_PageHeader" %>
<%@ OutputCache Duration="60" Shared="true" VaryByParam="none" %>
<div class="pageheader">
    <a href="/"><h1>ASP.NET项目通用模板</h1></a>
</div>

代码中,使用Control指令定义控件参数,并使用OutputCache指令指定缓存参数,其中,Duration参数指定缓存的秒数,请注意,系统资源紧张时会自动释放缓存资源;Shared参数指定是否在所有页面中共享此控件缓存,类似页眉、页脚这样的控件,可以设置为true;VaryByParam参数设置那些参数分别共享,这里设置为none,即不区分参数。

使用.ascx控件时应用注意,对于内容固定的控件,可以使用OutputCache指令设置缓存,而对于需要动态处理的控件,则不应使用缓存,如使用Web控件组合的复合类型的数据处理控件。

下面的代码(/lib/ctr/PageFooter.ascx)创建了页脚控件。

HTML
<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="PageFooter.ascx.cs" Inherits="lib_ctr_PageFooter" %>
<%@ OutputCache Duration="60" Shared="true" VaryByParam="none" %>
<div class="pagefooter">
    <p>caohuayu.com©</p>
</div>

和C#类控件一样,.ascx控件同样需要注册后使用,如下面的代码,我们在Web.config文件中的pages节点中添加控件注册节点。

XML
<pages controlRenderingCompatibilityVersion="4.0">
  <controls>
    <add tagPrefix="acl" namespace="acl.web.ctr" />
    <add tagPrefix="acl" tagName="PageHeader" src="/lib/ctr/PageHeader.ascx"/>
    <add tagPrefix="acl" tagName="PageFooter" src="/lib/ctr/PageFooter.ascx"/>
  </controls>
</pages>

这里,同样使用add节点注册.ascx控件,其中的参数包括:

  • tagPrefix,设置控件定义前缀。
  • tagname,设置控件定义名称。
  • src,指定.ascx文件路径。

下面的代码,我们在/Default.aspx页面中测试页眉、页脚控件的使用。

HTML
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <acl:PageHeader ID="header1" runat="server" />
    <form id="form1" runat="server">
        <div>
        </div>
    </form>
    <acl:PageFooter ID="footer1" runat="server" />
</body>
</html>

页面显示效果如图4。

图4