asp.net控件开发基础

asp.net本身提供了很多控件,提供给我们这些比较懒惰的人使用,我认为控件的作用就在此,因为我们不想重复工作,所以要创建它,这个本身便是一个需求的关系,所以学习控件开发很有意思.

wrox网站上有本书 Professional ASP.NET 2.0 Server Control and Component Development

现在还没有出版,但网站上放出了代码,所以正好下载过来学习一下.

我看过前几章代码,环环相扣,作者用不同的知识向我们展示同一个效果,所以循序渐进的学下来很有好处.

虽然自己对控件开发还不是很熟悉,但我感觉以下几点很重要,是我自己总结的

1.了解控件之间的继承关系

  最好是先看看看System.Web.UI命名空间

(1)Control 类,所有的控件都共享的一个类,你需要去看下其里面受保护的几个方法和属性,虽然一下看不完,以后会发现常常用到这些方法

大家可以在MSDN看一下其派生类

(2)HtmlTextWriter 类

不得不了解的一个类,主要工作就是我们写的标记字符和文本输出

2.重写方法

(1) 必须继承Control类
(2) 重写Control类的Render方法,这个是必须的,因为其他控件都继承了Control 类类,所以几乎所有控件都有这个方法

3.熟悉元数据

大家都知道asp.net控件属性在编辑器上是分类的,如外观,行为,布局等,每个属性还给出了解释

简单的元数据就是起到这个作用,当然你也可以不加,但使用了元数据让人感到有亲切感,写法如

[CategoryAttribute("Appearance")]

要使用元数据,必须引用System.ComponentModel命名控件,一般你如果写组件的话,不可能不用到这样类库。具体的MSDN上有所介绍。

一.输出字符串
说多了没意思,还是来演练吧。首先你得了解HTML。来看下面代码,效果就是输出HTML到客户端

示例一


using System;
using System.Web.UI;

namespace CustomComponents
{
  /** <summary>
  /// Summary description for CreditCardForm
  /// </summary>
  public class CreditCardForm1 : Control
  {
    protected override void Render(HtmlTextWriter writer)
    {
      writer.Write("<table style='width:287px;height:124px;border-width:0;'>");
      writer.Write("<tr>");
      writer.Write("<td><strong>Payment Method</strong></td>");
      writer.Write("<td>");
      writer.Write("<select name='PaymentMethod' id='PaymentMethod' style='width:100%;'>");
      writer.Write("<option value='0'>Visa</option>");
      writer.Write("<option value='1'>MasterCard</option>");
      writer.Write("</select>");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td><strong>Credit Card No.</strong></td>");
      writer.Write("<td><input name='CreditCardNo' id='CreditCardNo' type='text' /></td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td><strong>Cardholder's Name</strong></td>");
      writer.Write("<td><input name='CardholderName' id='CardholderName' type='text' /></td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td><strong>Expiration Date</strong></td>");
      writer.Write("<td>");
      writer.Write("<select name='Month' id='Month'>");
      for (int day = 1; day < 13; day++)
      {
        if (day < 10)
          writer.Write("<option value='" + day.ToString() + "'>" + "0" + day.ToString() + "</option>");
        else
          writer.Write("<option value='" + day.ToString() + "'>" + day.ToString() + "</option>");
      }
      writer.Write("</select>");
      writer.Write("&nbsp");
      writer.Write("<select name='Year' id='Year'>");
      for (int year = 2005; year < 2015; year++)
      {
        writer.Write("<option value='" + year.ToString() + "'>" + year.ToString() + "</option>");
      }
      writer.Write("</select>");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td align='center' colspan='2'>");
      writer.Write("<input type='submit' value='Submit' />");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("</table>");

      base.Render(writer);
    }
  }
}

 

效果很简单,其实就一直在输出HTML再加几个属性,大家可以直接把代码放在App_Code文件夹里,就可自动编译,当然也可以创建web控件库.
注意要继承Control类,重写Render方法,用HtmlTextWriter类的Write输出HTML

使用控件

(1).需要先注册一下

<%@ Register TagPrefix="custom" Namespace="CustomComponents" %>

(2) 然后就使用标签输出效果

<custom:CreditCardForm1 runat="server" ID="ccf" />

下为效果图

 

二.改善,加入属性和元数据

可能上面做出的 控件毫无用处,但却可以让你熟悉一下步骤,上面的控件定的很死,没有定义任何属性,用处不大,下面来改造

我们来定义常用属性,然后再输出,这样我们就可以修改控件的属性了,

示例二


using System;
using System.Web.UI;
using System.ComponentModel;

namespace CustomComponents
{
  [DefaultPropertyAttribute("CardholderNameText")]
    [ToolboxData(@"<{0}:CreditCardForm2
    PaymentMethodText='信用卡类型' CreditCardNoText='信用卡卡号'
    CardholderNameText='信用卡持有者姓名' SubmitButtonText = '提交' 
    runat='server'></{0}:CreditCardForm2>")
    ]
  public class CreditCardForm2 : Control
  {
    private string paymentMethodText = "信用卡类型";
    private string creditCardNoText = "信用卡卡号";
    private string cardholderNameText = "信用卡持有者姓名";
    private string expirationDateText = "最后使用时间";
    private string submitButtonText = "提交";

    [BrowsableAttribute(true)]
    [DescriptionAttribute("获取和设置信用卡类型")]
      [DefaultValueAttribute("信用卡类型")]
    [CategoryAttribute("Appearance")]
    public virtual string PaymentMethodText
    {
      get { return this.paymentMethodText; }
      set { this.paymentMethodText = value; }
    }

    [BrowsableAttribute(true)]
    [DescriptionAttribute("获取或设置信用卡卡号")]
    [DefaultValueAttribute("信用卡卡号")]
    [CategoryAttribute("Appearance")]
    public virtual string CreditCardNoText
    {
      get { return this.creditCardNoText; }
      set { this.creditCardNoText = value; }
    }

    [BrowsableAttribute(true)]
      [DescriptionAttribute("获取或设置信用卡持有者姓名")]
    [DefaultValueAttribute("信用卡持有者姓名")]
    [CategoryAttribute("Appearance")]
    public virtual string CardholderNameText
    {
      get { return this.cardholderNameText; }
      set { this.cardholderNameText = value; }
    }

    [BrowsableAttribute(true)]
      [DescriptionAttribute("获取或设置最后使用时间")]
      [DefaultValueAttribute("最后使用时间")]
    [CategoryAttribute("Appearance")]
    public virtual string ExpirationDateText
    {
      get { return this.expirationDateText; }
      set { this.expirationDateText = value; }
    }

    [BrowsableAttribute(true)]
    [DescriptionAttribute("获取或设置按钮标签")]
    [DefaultValueAttribute("提交")]
    [CategoryAttribute("Appearance")]
    public virtual string SubmitButtonText
    {
      get { return this.submitButtonText; }
      set { this.submitButtonText = value; }
    }

    protected override void Render(HtmlTextWriter writer)
    {
      writer.Write("<table style='width:287px;height:124px;border-width:0;'>");
      writer.Write("<tr>");
      writer.Write("<td>" + PaymentMethodText + "</td>");
      writer.Write("<td>");
      writer.Write("<select name='PaymentMethod' id='PaymentMethod' style='width:100%;'>");
      writer.Write("<option value='0'>Visa</option>");
      writer.Write("<option value='1'>MasterCard</option>");
      writer.Write("</select>");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td>" + CreditCardNoText + "</td>");
      writer.Write("<td><input name='CreditCardNo' id='CreditCardNo' type='text' /></td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td>" + CardholderNameText + "</td>");
      writer.Write("<td><input name='CardholderName' id='CardholderName' type='text' /></td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td>" + ExpirationDateText + "</td>");
      writer.Write("<td>");
      writer.Write("<select name='Month' id='Month'>");
      for (int day = 1; day < 13; day++)
      {
        if (day < 10)
          writer.Write("<option value='" + day.ToString() + "'>" + "0" + day.ToString() + "</option>");
        else
          writer.Write("<option value='" + day.ToString() + "'>" + day.ToString() + "</option>");
      }
      writer.Write("</select>");
      writer.Write("&nbsp");
      writer.Write("<select name='Year' id='Year'>");
      for (int year = 2005; year < 2015; year++)
      {
        writer.Write("<option value='" + year.ToString() + "'>" + year.ToString() + "</option>");
      }
      writer.Write("</select>");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("<tr>");
      writer.Write("<td align='center' colspan='2'>");
      writer.Write("<input type='submit' value='" + SubmitButtonText + "' />");
      writer.Write("</td>");
      writer.Write("</tr>");
      writer.Write("</table>");

      base.Render(writer);
    }
  }
}

 

上面我们接触到了元数据了,意思应该很好理解,为了测试元数据的作用,大家可以新建一个类库项目,然后把写的代码放这个项目里面,接着web网站引用这个项目,成功生成以后,你会发现工具箱已经自动帮你加上了这几个控件

 

 

接着你要做的工作就是拖动你需要的控件,然后你会在属性面板看到下图


然后你再结合代码中的元数据,应该就知道大概意思了.(可以根据你的理解结合MSDN看)


三.再次改善,淘汰用Write方法以字符串的方式输出HTML


接着我们继续发现问题,我们发现我们除了定义几个需要自己来修改的属性外,还是要用来大量的字符串用来输出HTML,而且容易输错.所以HtmlTextWriter类提供几个有用的方法用来代替.

(1)AddStyleAttribute方法 为标签添加样式属性
(2)AddAttribute方法        为标签添加属性
(3)RenderBeginTag          开始写入标签头 如<table....>
(4)RenderEndTag            写入标签尾部,如</table>

这里有几点需要特别注意.

一.因为其定义方式跟我们平时定义方式不同,我们平时写HTML时,是先写标签开头,再写标签的属性.如<table borderwidth="0">,然而我们在使用上面几个方法时,需要有先后顺序,我们需要先定义标签的属性和样式,然后再输出标签头.

二.标签头和尾,需一一对应.可以理解为嵌套关系.最好的理解方法就是输出代码后,查看源文件,再结合原来定义的代码来看.

还是看代码比较容易说明,由于CreditCardForm2已经定义了我们需要的属性,而我们现在要做的只是用标签的形式来替代字符串的形式,所以只需要继承CreditCardForm2类,重写Render方法即可

示例三


protected override void Render(HtmlTextWriter writer)
    {
      writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "0");
      writer.RenderBeginTag(HtmlTextWriterTag.Table);
      writer.RenderBeginTag(HtmlTextWriterTag.Tr);
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.Write("<strong>" + PaymentMethodText + "</strong>");
      writer.RenderEndTag();
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.AddAttribute(HtmlTextWriterAttribute.Name, "PaymentMethod");
      writer.AddAttribute(HtmlTextWriterAttribute.Id, "PaymentMethod");
      writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%");
      writer.RenderBeginTag(HtmlTextWriterTag.Select);

      writer.AddAttribute(HtmlTextWriterAttribute.Value, "0");
      writer.RenderBeginTag(HtmlTextWriterTag.Option);
      writer.Write("Visa");
      writer.RenderEndTag();

      writer.AddAttribute(HtmlTextWriterAttribute.Value, "1");
      writer.RenderBeginTag(HtmlTextWriterTag.Option);
      writer.Write("MasterCard");
      writer.RenderEndTag();

      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();

      writer.RenderBeginTag(HtmlTextWriterTag.Tr);
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.Write("<strong>" + CreditCardNoText + "</strong>");
      writer.RenderEndTag();
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.AddAttribute(HtmlTextWriterAttribute.Name, "CreditCardNo");
      writer.AddAttribute(HtmlTextWriterAttribute.Id, "CreditCardNo");
      writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
      writer.RenderBeginTag(HtmlTextWriterTag.Input);
      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();

      writer.RenderBeginTag(HtmlTextWriterTag.Tr);
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.Write("<strong>" + CardholderNameText + "</strong>");
      writer.RenderEndTag();
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.AddAttribute(HtmlTextWriterAttribute.Name, "CardholderName");
      writer.AddAttribute(HtmlTextWriterAttribute.Id, "CardholderName");
      writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
      writer.RenderBeginTag(HtmlTextWriterTag.Input);
      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();

      writer.RenderBeginTag(HtmlTextWriterTag.Tr);
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.Write("<strong>" + ExpirationDateText + "</strong>");
      writer.RenderEndTag();
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.AddAttribute(HtmlTextWriterAttribute.Name, "Month");
      writer.AddAttribute(HtmlTextWriterAttribute.Id, "Month");
      writer.RenderBeginTag(HtmlTextWriterTag.Select);

      for (int day = 1; day < 13; day++)
      {
        writer.AddAttribute(HtmlTextWriterAttribute.Value, day.ToString());
        writer.RenderBeginTag(HtmlTextWriterTag.Option);

        if (day < 10)
          writer.Write("0" + day.ToString());
        else
          writer.Write(day);

        writer.RenderEndTag();
      }

      writer.RenderEndTag();
      writer.Write("&nbsp;");

      writer.AddAttribute(HtmlTextWriterAttribute.Name, "Year");
      writer.AddAttribute(HtmlTextWriterAttribute.Id, "Year");
      writer.RenderBeginTag(HtmlTextWriterTag.Select);

      for (int year = 2005; year < 2015; year++)
      {
        writer.AddAttribute(HtmlTextWriterAttribute.Value, year.ToString());
        writer.RenderBeginTag(HtmlTextWriterTag.Option);
        writer.Write(year);
        writer.RenderEndTag();
      }

      writer.RenderEndTag();

      writer.RenderEndTag();
      writer.RenderEndTag();

      writer.RenderBeginTag(HtmlTextWriterTag.Tr);
      writer.AddAttribute(HtmlTextWriterAttribute.Align, "center");
      writer.AddAttribute(HtmlTextWriterAttribute.Colspan, "2");
      writer.RenderBeginTag(HtmlTextWriterTag.Td);
      writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit");
      writer.AddAttribute(HtmlTextWriterAttribute.Value, SubmitButtonText);
      writer.RenderBeginTag(HtmlTextWriterTag.Input);
      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();
      writer.RenderEndTag();
    }

 

实现的效果虽然一样,但上面的代码是不是漂亮很多,而且不容易输错.这也是所提倡的做法

四.未使用视图状态的后果

还是视图状态,关于视图状态大家可以参考MSDN和相关文章

看以下的示例,还是CreditCardForm3这个控件

 

if (!IsPostBack)
    {
      creditcardform.CardholderNameText = "Full Name";
      creditcardform.CreditCardNoText = "CreditCardNo";
      creditcardform.ExpirationDateText = "ExpirationDate";
      creditcardform.PaymentMethodText = "Payment Options";
      creditcardform.SubmitButtonText = "Send";
    }
 

首次加载效果


点击按钮以后


 

五.使用视图状态改善效果

前提条件是你未禁用视图状态

继承CreditCardForm3,改写每个属性


 


public override string PaymentMethodText
    {
        get { return ViewState["PaymentMethodText"] != null ? (string)ViewState["PaymentMethodText"] : "信用卡类型"; }
      set { ViewState["PaymentMethodText"] = value; }
    }

    public override string CreditCardNoText
    {
        get { return ViewState["CreditCardNoText"] != null ? (string)ViewState["CreditCardNoText"] : "信用卡卡号"; }
      set { ViewState["CreditCardNoText"] = value; }
    }

    public override string CardholderNameText
    {
        get { return ViewState["CardholderNameText"] != null ? (string)ViewState["CardholderNameText"] : "信用卡持有者姓名"; }
      set { ViewState["CardholderNameText"] = value; }
    }

    public override string ExpirationDateText
    {
        get { return ViewState["ExpirationDateText"] != null ? (string)ViewState["ExpirationDateText"] : "最后使用时间"; }
      set { ViewState["ExpirationDateText"] = value; }
    }

    public override string SubmitButtonText
    {
        get { return ViewState["SubmitButtonText"] != null ? (string)ViewState["SubmitButtonText"] : "提交"; }
      set { ViewState["SubmitButtonText"] = value; }
    }


以上全为个人见解,如有错误,希望大家指出.

点击下载代码

上一篇:http://www.cnblogs.com/Clingingboy/archive/2006/07/30/463471.html
或许大家还对为何要重写Render方法存有疑惑,希望大家看看我举的例子,能够明白Render方法和其他两个方法的作用,然后真正明白为何一般情况下只须重写Render方法

我们知道我们每次编写控件时,都需要重写Render方法,我们发现在Control类中很多方法可以重写,但我们没有去重写他们,我们需要遵循一个原则,在需要重载的时候再去重写他们

我们还是先来看看与Render方法相关的两个方法

//RenderControl方法的基本实现
 public void RenderControl(HtmlTextWriter writer)
 {
 if(Visible)
 {
 Render(writer);
 }
 }
 //Render方法基本实现
 protected virtual void Render(HtmlTextWriter writer)
 {
 RenderChildren(writer);
 }
 //RenderChildren方式基本实现
 protected virtual void RenderChildren(HtmlTextWriter writer)
 {
 foreach (Control c in Controls)
 {
 c.RenderControl(writer);
 }
 }

相信看过"ASP.NET服务器控件开发技术与实例"这本书的人,肯定看过上面的一段代码.

假设你不理解上面的流程(我也不一定理解,希望我的思路对你有帮助),我认为有一种很好的方式来理解上面的流程,跟大家分享一下.

现在抛开上面的代码,我们来建一个简单的页面,随意的拖几个控件到界面上,注意最后一个三panel控件,如下图


图一

我们知道,每个控件都有Visible和EnableViewState属性,Visible用来设置控件是否被呈现.


图二

现在我们把button控件的Visible属性设置为flase,我们看到了我们预期的效果,接着请启用页面跟踪,这个很重要


图三

在服务器上运行这个页面,大家可以在控件树上看到下面画面


图四

(1)System.Web.UI.LiteralControl

大家可以看到,在我们定义的每个控件之间都有System.Web.UI.LiteralControl.
这里需要说明的是,要理解任何不需要在服务器上处理的任何其他字符串.

如何理解呢?大家打开这个运行页面的源代码页面,如下代码,大家看到没有,除了服务器控件外,我们有其他元素(不需要在服务器上处理的任何其他字符串),包括空格.

示例一 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
  鏃犳爣棰橀〉
</title></head>
<body>
 <form name="form1" method="post" action="Default1.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTExNTUxMDYxODdkZHVaWm47e5anDettRKviGvS0nDWQ" />
</div>

 <div>
 <span id="Label1">Label</span><br />
 <br />
 <input name="TextBox1" type="text" id="TextBox1" /><br />
 <br />
 <br />
 <br />
 <div id="Panel1" style="height:50px;width:125px;">
 
 
</div>
 &nbsp;</div>
 
<div>

  <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgK/5/fTBwLs0bLrBrVw7YrSp5G/l4sJGPkKN/asFj2W" />
</div></form>
</body>
</html>

为了让大家更加明白System.Web.UI.LiteralControl的意思的,让我们来修改HTML页面,说明:以上代码为运行后的HTML源代码.而不是我们所说的源代码,大家应该明白我所指的源代码的意思.

我们来修改代码,注意:我把<form..以下的标签无空格的写在了一起.看下面修改后的代码

示例二 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" Trace="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
 <title>无标题页</title>
</head>
<body>
 <form id="form1" runat="server"><asp:Label ID="Label1" runat="server" Text="Label"></asp:Label><asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><asp:Button ID="Button1" runat="server" Text="Button" Visible="False" /><asp:Panel ID="Panel1" runat="server" Height="50px" Width="125px"></asp:Panel></form>
</body>
</html>

运行效果


图五

现在发现控件之间已经没有System.Web.UI.LiteralControl了,因为我去掉了空格.这个也说明了一点,如果代码很乱的话会影响速度.现在大家应该明白System.Web.UI.LiteralControl的意思了吧.

(2)大家继续看图四的Button1,大家会发现它呈现的大小字节数为0,因为我们设置了Button1的Visible值为False,所以未呈现此控件.

下面我们来理解这一点,大家重新看到RenderControl方法,如果Visible值为True则呈现此控件.

if(Visible)
 Render(writer);

为了理解这个方法,我们来重写此方法,我们以第一次讲的CreditCardForm3控件为例

我们重写RenderControl方法,把Render方法的代码全部拷贝到RenderControl方法中,然后去掉Render方法

然后在asp.net页面使用此控件,定义其Visible值为False

图六

运行这个例子以后,你会发现控件还是呈现了,就是因为你重写了RenderControl方法,使控件的Visible值无效了

所以我们就要加上一个判断

if(Visible) {}


否则的话,此方法呈现的内容没有Visible值.为了更加深刻理解这一点,我们重写基类的RenderControl方法的方法.


base.RenderControl(writer);

你会发现在页面呈现时的控件有两个,一个在RenderControl方法方法输出,一个在Render方法输出,因为
base.RenderControl方法调用了Render方法,当设置控件Visible属性为False时,Render方法输出的内容被隐藏(未被呈现,而RenderControl方法输出的内容仍然存在.现在大家应该了解RenderControl方法的作用了吧.

如果服务器控件的 Visible 属性设置为 true,则向页呈现服务器控件的内容,所以一般情况下我们不重写此方法.因为一般控件都需要Visible 属性,除非特殊情况.
图七

(3)RenderChildren方法

再重新看到图四,大家可以看到,我们拖放的控件是在属于form1的子控件,panel控件是一个容器控件,因为下面没拖放控件,任何其他显示的字符串表现为System.Web.UI.LiteralControl,大家可以拖几个控件到panel里再重新运行看看,会发现拖进去的控件变为panel的子控件.最明显的的测试方法是Wizard控件,拖放一个Wizard控件然后再测试你就会明白了.

RenderChildren方法则判断当前控件是否有子控件,如果有,则根据RenderControl方法判断控件的Visible值来呈现控件.所以大家在重写Render方法时,不重写基类Render方法时,将无法实现RenderChildren方法.带来的后果将是无法呈现子控件.

下面我们来测试一下.我们还是以CreditCardForm3控件为例子(请先把RenderControl方法的内容全注释掉),当未重实现RenderChildren方法时则无法呈现子控件内容,请启动跟踪

将发现其子控件呈现字节为0

图八

由于CreditCardForm3继承了CreditCardForm2,所以重写基类Render方法将会重复输出,我们可以直接在Render方法中重写RenderChildren方法.再来测试.将会发现有些变化

发现其子控件呈现字节并非为0,而是10

图九

说明其子控件还是存在东西的,只不过没有用而已,所以大家可以根据实际需求来确实是否要重写RenderChildren方法,一般的话都会重写Render方法,这样保险一点

好了,现在再来回顾下刚开始给出的代码,通过上面的试验,你是否明白了?

呈现控件的步骤(注意:下面三个方法都可以呈现,不过我们已经说过了,像在RenderControl方法用HtmlTextWriter预先输出的话,就丧失Visible的功能(说不定你就不需要这个功能,那时你就可以重写这个方法了)

(1)RenderControl方法

先判断其Visible然后调用Render方法

(2) Render方法

使用HtmlTextWriter将标记字符和文本输出然后调用RenderChildren方法

(3)RenderChildren方法

判断当前控件是否有子控件,然后再调用RenderControl方法根据子控件的Visible值输出子控件.

我们了解上面三个方法后,就会知道,一般情况下,我们无须重写RenderControl方法和RenderChildren方法.所以最合适的就是重写Render方法了.说了一大堆.目的就是为了说明为什么要重写Render方法.

上次,忘了把代码传上了,不小心只上传了dll文件,不好意思.这次就写这么多.希望大家能够真正明白.大家可以适当的修改代码,这样你会发现更多.

参考文章:ASP.NET2.0服务器控件之Render方法

点击下载示例代码

如果有什么错误请大家指出,希望多跟大家交流^_^
上一篇:http://www.cnblogs.com/Clingingboy/archive/2006/08/01/465397.html
上次讲了在继承Control类的时候为什么需要重写Render方法

本次来介绍控件的事件处理. 我们知道Button控件有OnClick事件,DropDownList控件有SelectedIndexChanged事件.

一.回发事件和客户端回发

下面来看一个最简单的例子

按钮单击事件


1 protected void Button1_Click(object sender, EventArgs e)
2     {
3         Label1.Text = "你好: "+TextBox1.Text;
4     }

大家知道Web 服务器控件创建的按钮的类型有三种

1.Button
2.LinkButton
3.ImageButton

打开MSDN看到三个控件都继承IPostBackEventHandler接口


IPostBackEventHandler接口专门定义了处理回发事件的方法,说白了就是onclick事件,如果自定义控件需要处理回发事件,你就需要继承IPostBackEventHandler接口,然后实现接口的RaisePostBackEvent 方法,另外一个简单的方法就是直接继承Button控件就可以了.

RaisePostBackEvent方法用于处理窗体发送给服务器时引发的事件,方法中有一个参数eventArgument 表示要传递到事件处理程序的可选事件参数的 String

下面总结处理回发事件,必须要做的步骤

(1)继承并实现IPostBackEventHandler接口的RaisePostBackEvent方法

(2)为表单元素定义UniqueID,以与IPostBackEventHandler服务器控件的UniqueID相对应

相应实现代码如下

示例一


namespace CustomControls
{
    public class SuperButton1 : Control, IPostBackEventHandler
    {
        // 声明Click事件委托
        public event EventHandler Click;

        // 定义OnClick事件处理程序
        protected virtual void OnClick(EventArgs e)
        {
            if (Click != null)
            {
                Click(this, e);
            }
        }

        // 实现RaisePostBackEvent方法,处理回发事件
        public void RaisePostBackEvent(string eventArgument)
        {
            OnClick(EventArgs.Empty);
        }

        protected override void Render(HtmlTextWriter output)
        {
            output.Write("<INPUT TYPE=submit name=" + this.UniqueID +
               " Value='确定' />");
        }
    }
}

如果你不熟悉委托的话,可以参考一篇叫一个C#睡前故事的文章

EventArgs.Empty表示没有事件数据的事件,不要跟我以前一样认为是一个空的事件,当时就很郁闷,干什么要触发空事件呢,都是因为没看清楚Empty字段的意思,以为就为空的意思了.

EventArgs.Empty等同于EventArgs类的构造函数,等同于new EventArgs()
注意还在呈现控件的name属性加了UniqueID.
好了,现在你可以测试下了.


1protected void SuperButton1_1_Click(object sender, EventArgs e)
2    {
3        Label1.Text = "你点击了此按钮";
4    }


这样你就成功定义了一个处理回发事件的控件. 假设你在页面上多次使用这个控件,编译器将为每个事件委托实例生成一个字段。如果事件的数目很大,则一个委托一个字段的存储成本可能无法接受。.所以推荐采用另外一种优化的事件实现

EventHandlerList 类提供一个简单的委托列表来添加和删除委托,下面来看看更改后的代码,

AddHandler有两个参数事件对象和添加的委托,在OnClick事件中必须显示将委托转换为EventHandler类型

示例二


using System;
using System.Web.UI;

namespace CustomComponents
{
    public class SuperButton2 : Control, IPostBackEventHandler
    {
        // 声明Click事件委托
        private static readonly object ClickKey = new object();

        public event EventHandler Click
        {
            add
            {
                Events.AddHandler(ClickKey, value);
            }
            remove
            {
                Events.RemoveHandler(ClickKey, value);
            }
        }

        // 定义OnClick事件处理程序
        protected virtual void OnClick(EventArgs e)
        {
            EventHandler clickEventDelegate =
               (EventHandler)Events[ClickKey];
            if (clickEventDelegate != null)
            {
                clickEventDelegate(this, e);
            }
        }

        // 实现RaisePostBackEvent方法,处理回发事件
        public void RaisePostBackEvent(string eventArgument)
        {
            OnClick(new EventArgs());
        }

        protected override void Render(HtmlTextWriter output)
        {
            output.Write("<INPUT TYPE=submit name=" + this.UniqueID +
               " Value='确定' />");
        }
    }
}

下面再来说下客户端回发事件,在HTML窗体元素中只有Button按钮和ImageButton才可以引起窗体回发.

但如LinkButton链接按钮控件要希望启动回发的话,则要依赖客户端脚本的事件机制来实现其功能.

在asp.net2.0中,button控件多了一个UseSubmitBehavior 属性,指示 Button 控件使用客户端浏览器的提交机制(客户端回发)还是 ASP.NET 回发机制,默认采用回发机制,如果设置为false的话,则需要调用GetPostBackEventReference 方法来返回 Button 的客户端回发事件

当设置UseSubmitBehavior 属性为flase时,你运行页面时,则会发现一段自动生成的javascript代码

LinkButton也一样,再看下面例子,定义了枚举,定义button按钮和链接按钮,大家在测试的时候,打开源代码就会发现不同效果
示例三


using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace CustomComponents
{
    public enum ButtonDisplay
    {
        Button = 0,
        Hyperlink = 1
    }

    [ToolboxData("<{0}:SuperButton3 runat=server></{0}:SuperButton3>")]
    public class SuperButton3 : Control, IPostBackEventHandler
    {
        public virtual ButtonDisplay Display
        {
            get
            {
                object display = ViewState["Display"];
                if (display == null)
                    return ButtonDisplay.Button;
                else
                    return (ButtonDisplay)display;
            }
            set
            {
                ViewState["Display"] = value;
            }
        }

        public virtual string Text
        {
            get
            {
                object text = ViewState["Text"];
                if (text == null)
                    return string.Empty;
                else
                    return (string)text;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }

        private static readonly object ClickKey = new object();

        public event EventHandler Click
        {
            add
            {
                Events.AddHandler(ClickKey, value);
            }
            remove
            {
                Events.RemoveHandler(ClickKey, value);
            }
        }

        protected virtual void OnClick(EventArgs e)
        {
            EventHandler clickEventDelegate =
               (EventHandler)Events[ClickKey];
            if (clickEventDelegate != null)
            {
                clickEventDelegate(this, e);
            }
        }

        public void RaisePostBackEvent(string argument)
        {
           
            OnClick(EventArgs.Empty);
        }

        override protected void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            Page.VerifyRenderingInServerForm(this);

            if (Display == ButtonDisplay.Button)
            {
                writer.Write("<INPUT type=\"submit\"");
                writer.Write(" name=\"" + this.UniqueID + "\"");
                writer.Write(" id=\"" + this.UniqueID + "\"");
                writer.Write(" value=\"" + Text + "\"");
                writer.Write(" />");
            }
            else if (Display == ButtonDisplay.Hyperlink)
            {
                writer.Write("<A href=\"");
                writer.Write(Page.GetPostBackClientHyperlink(this, ""));
                writer.Write("\">" + Text + "</A>");
            }
        }
    }
}


如果大家本来就学过这方面的知识,看了心里还有谱,如果没有的话,里面有些方法不熟悉的话,还是要多看看MSDN. 说通俗点,回发事件可以就理解为按钮单击事件,而按钮又分两种不同的回发事件方法,这样讲的话,更容易让人接受,而上面所讲的就是实现按钮单击事件实现的方法.

二.数据回发事件

好了,接着再讲数据回发.跟上面讲的事件回发有点不同.

下面也举一个简单的例子,看下图,有两个DropDownList,一个开启AutoPostBack,一个没有开启,再接着看下面简单的代码,第一个DropDownList,改变下拉框值时,label没显示,按确定按钮后则显示label,第二个DropDownList改变下拉框值时就显示了label,因为开启了AutoPostBack.这个大家都明白吧.

 

 protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
    {
        Label2.Text = "你选择了:  " + DropDownList1.SelectedItem.Text;
    }
    protected void DropDownList2_SelectedIndexChanged(object sender, EventArgs e)
    {
        Label1.Text = "你选择了:  " + DropDownList2.SelectedItem.Text;
    }


以上实现的原理就是在SelectedIndexChanged事件里,判断旧值和新值的比较(比较数据),如果发生变化,则引发事件,数据回发就是实现这样的事件.再重新整理一下思路,明白何时会引发SelectedIndexChanged事件

在选择下拉框值时,如果选的值跟原来的值相同,则不触发事件,如果选的值跟原来的值不同的话则触发SelectedIndexChanged事件(还是旧值和新值的比较).

打开MSDN文档查看DropDownList 类,则发现其继承了 IPostBackDataHandler 接口,我的意思就是说想要实现Change这样的事件,就要继承其接口.看看MSDN对此接口的定义

IPostBackDataHandler 接口
定义 ASP.NET 服务器控件为自动加载回发数据而必须实现的方法。

LoadPostData 方法  根据服务器控件的状态由于回发而发生更改做出判断是否调用RaisePostDataChangedEvent 方法,返回true则调用(就是旧值和新值不同的时候)

RaisePostDataChangedEvent 方法用于引发任何更改事件

以下的例子实现了如同textbox的TextChanged事件


postDataKey表示控件内部数据的关键值,postCollection表示所有传入名称值的集合,其采用索引的方式来访问


 


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

namespace CustomComponents
{
    [ToolboxData("<{0}:Textbox1 runat=server></{0}:Textbox1>"),
    DefaultProperty("Text")]
    public class Textbox1 : Control, IPostBackDataHandler
    {
        public string Text
        {
            get
            {
                object text = ViewState["Text"];
                if (text == null)
                    return string.Empty;
                else
                    return (string)text;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }

        public bool LoadPostData(string postDataKey,
           NameValueCollection postCollection)
        {
            string postedValue = postCollection[postDataKey];
            //检查新旧数据
            if (!Text.Equals(postedValue))
            {
                Text = postedValue;
                return true;
                //自动调用RaisePostDataChangedEvent()
            }
            else
                return false;
            //不发生变化
        }
        public void RaisePostDataChangedEvent()
        {
            OnTextChanged(EventArgs.Empty);
        }

        protected virtual void OnTextChanged(EventArgs e)
        {
            if (TextChanged != null)
                TextChanged(this, e);
        }

        public event EventHandler TextChanged;

        override protected void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            Page.VerifyRenderingInServerForm(this);
            writer.Write("<INPUT type=\"text\" name=\"");
            writer.Write(this.UniqueID);
            writer.Write("\" value=\"" + this.Text + "\" />");
        }
    }
}

上面实现的方法如同button的onclick事件,其实不然,而是通过回发数据的新旧数据进行判断,我在示例代码中加了另外一个例子,这里就不列出了,大家可以下载后再去看,看了就明白不是button的onclick事件了.

本次主要讲了三个基础的事件处理

(1)捕获回发事件
(2)用于回调的客户端脚本
(3)处理回发数据

以下两个接口需要你慢慢的熟悉和使用

IPostBackEventHandler接口和IPostBackDataHandler 接口.

想到Button按钮就要想到IPostBackEventHandler接口,想要textbox,dropdownlist一些change事件则要想要IPostBackDataHandler 接口,如果结合起来,再自己思考的话,会明白的更深刻.

可能很多地方我也没表达清楚,跟别人讲的很多重复了,但还要拿出来分享下,这样也可以提高自己.最后还望大家如果看到有什么错误,请指出.

参考文章:

ASP.NET2.0服务器控件开发之实现事件

ASP.NET2.0服务器控件之捕获回传事件


ASP.NET 2.0服务器控件之处理回传数据


本文示例代码下载
 上一篇写了有关回传的一些东西,这次我本来不知道该写什么的,因为各方面的关联太多了,最后我还是想,还是慢慢一点点的写吧.这次讲WebControl

一.从继承WebControl开始

在第二篇教程中,重点介绍了Render()方法的使用,用来呈现控件,但从Control类继承的控件尚未发挥asp.net控件的作用.大家知道web服务器控件分为HTML服务器控件(如<input id="Button2" runat="server" type="button" value="button" />这样的形式)和标准服务器控件(就是<asp:..  id="" runat="server" />这样的形式的控件)

HTML服务器控件的控件从System.Web.UI.HtmlControls.HtmlControl 类派生

标准服务器控件的控件从System.Web.UI.WebControls.WebControl 类派生

HtmlControl 类和WebControl 类则从System.Web.UI.Control 类派生,并扩展.

所以我们说,所有的服务器控件都继承自System.Web.UI.Control 类,即所有的服务器控件都具有Control 类的共同属性,如Visible,EnableViewState属性,HtmlControl 类和WebControl 类则扩充了System.Web.UI.Control 类的功能,如

HtmlControl 类定义了所有 HTML 服务器控件所通用的方法、属性 (Property) 和事件(具体参数参照MSDN)

WebControl 类定义了所有 标准服务器控件所通用的方法、属性 (Property) 和事件(具体参数参照MSDN)

如每个继承了WebControl 类的标准控件都有定义外观和行为的属性,然后不同控件再根据需要扩展功能.

图一

所以我们推荐的做法是直接从WebControl 类派生,而非Control类.我们所做的非并从头开始.从WebControl 类继承可以帮我们省很多工作.

二.重写WebControl类方法,不再是Render()

WebControl类继承了Control类,当然有Render方法,在WebControl类中重写了Render方法,如下代码

示例一


protected override void Render(HtmlTextWriter output)
{
 RenderBeginTag(output);
 RenderContents(output);
 RenderEndTag(output);
}

注意 RebderBeginTag方法并非是HtmlTextWriter类中的方法,而是WebControl类中的方法,表示输出HTML标签头标记,如<table .....>,RenderEndTag方法则输出HTML标签尾标记,如</table>.中间的RenderContents方法则就是Control类的Render方法. 看下面RenderContents方法的定义.

示例二


protected override void RenderContents(HtmlTextWriter output){
 //使用默认逻辑来呈现子控件,那么一定要调用基类中的方法。
 base.Render(output);
}


接着再看RenderBeginTag方法的定义

示例三


      public virtual void RenderBeginTag(HtmlTextWriter output)
      {
          //添加呈现控件的属性和样式
          //AddAttributesToRender为WebControl类中的方法
          AddAttributesToRender(output);
          //呈现控件标签
          //如label控件呈现<span >
          //textbox控件呈现<input >
          HtmlTextWriterTag tagKey = TagKey;
          if (tagKey != HtmlTextWriterTag.Unknown)
          {
              output.RenderBeginTag(tagKey);
          }
          else
          {
              output.RenderBeginTag(this.TagName);
          }
      }

这里打个比方,假设你要输出一个表格,你就必须定义<table>标签头,然后在其内部定义<tr>,<td>,下面看Control类中Render方法的实现,表明Render方法必须完成所有的任务,包括标签头标记<table>和<table>标签的属性和样式的输出.

示例四


        protected override void Render(HtmlTextWriter writer)
        {
            //为table标签定义属性和样式
            writer.AddAttribute(HtmlTextWriterAttribute.Width, "287px");
            writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "0");

            writer.RenderBeginTag(HtmlTextWriterTag.Table);

           
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.Write("<strong>" + PaymentMethodText + "</strong>");
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            writer.AddAttribute(HtmlTextWriterAttribute.Name, "PaymentMethod");
            writer.AddAttribute(HtmlTextWriterAttribute.Id, "PaymentMethod");
            writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%");
            writer.RenderBeginTag(HtmlTextWriterTag.Select);

            //以下代码省略
    &nbs 

转载于:https://www.cnblogs.com/xiaoleiking/archive/2011/09/19/2180932.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值