visual|服务器|控件|日期
  一、序言 
  Visual C#.net是微软公司出品的一种新的编程语言(以下简称C#),它继承了C语言的一些特性,也加入了一些新的元素。以前用过Delphi开发程序的人可能刚开始使用C#的时候,对其有一种似曾相识的感觉(至少包括我)。是的,C#语言的创始人正是以前在Borland公司开发出Delphi语言的Anders Hejlsberg。在我开始使用C#开发程序时,就觉得它是一款很棒的开发Windows Form & Web程序的RAD工具。 
  在开发Web程序方面,C#的出现打破了以前的网页开发模式,实现了与开发Windows 
  Form程序一样的所见即所得的功能。C#提供了一些常用的Web Form Control供开发人员使用,并且只需将控件拖入页面中即可,非常简单。但有时这些控件也不能满足开发人员的需要,需要开发人员自己编写用户控件(User Control)或自定义控件(Custom Control)来满足需求。在这里,我将讲解如何在C#中开发服务器控件。 
  二、预备知识 
  在C#中可以开发两种服务器控件,一个是用户控件(User Control)和自定义控件(Custom Control)。用户控件的本质与页面文件(ASPx文件)差不多,是可被其它aspx页面重复使用的HTML代码段,当然它也包括后台代码(Code-behind),后缀名是ascx。所以在开发一些公用的静态页面时(例如页头,页脚)经常用到,但它的缺点是不易继承,不易分发,无法编译成二进制代码来进行部署。但是自定义控件的功能就强大许多,它可以被编译成二进制代码(DLL文件),可以被扩展、继承、分发。就像Web Form Control一样,其实它们每个控件就是一个DLL文件。 
  开发用户控件比较简单,就像编写一个aspx页面一样,在这里就不介绍了。本文对象是自定义控件。服务器控件的基类是System.Web.UI.Control。如果要开发可视化的服务器控件,那我们需要从System.Web.UI.WebControls来继承,否则从System.Web.UI.Control继承。 
  服务器控件在设计时以runat=”server”脚本代码嵌入到aspx文件中来表示此控件是在服务器端运行的。在服务器控件所在页面提交回传(PostBack)过程中是依靠ViewState(视图状态)来维护控件状态的。所以我们在设计服务器控件属性时,其值应保存在ViewState中。 
  三、代码编写 
  C#中有一个日历控件Calendar,但是现在我需要一个可以下拉的日历控件,并且初始时不显示日历,当我点击下拉按钮时才弹出,并且当选择了日期,日历会自动隐藏且选择的日期值会显示到相应的输入框中。显然Calendar控件不能满足我的需要,但是稍后我会在我的自定义控件中用到它。 
  首先新建项目,在项目类型中选择Visual C#项目,在模板列表中选择Web控件库,输入项目名称AquaCalendar,然后选择项目所在目录,点击【确定】按钮。C#将会生成基本的框架代码。将项目中的类文件和类名改名为DatePicker(即日期控件的类名)。由于DatePicker是可视化控件,所以我们必须从System.Web.UI.WebControls继承。并且它包括一个输入框,一个按钮和日历控件,需要在DatePicker类中声明它们。像这种以多个服务器控件组合的控件成为复合控件。代码如下,比较重要的方法和代码在注释中会加以说明:
using System; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using System.ComponentModel; 
using System.Drawing; 
namespace AquaCalendar 
{ 
 [DefaultProperty("Text"), //在属性工具箱中显示的默认属性 
 ToolboxData("<{0}:DatePicker runat=server>")] 
 public class DatePicker : System.Web.UI.WebControls.WebControl , IPostBackEventHandler 
 { 
  //选择日期按钮的默认样式 
  private const string _BUTTONDEFAULTSTYLE = "BORDER-RIGHT: gray 1px solid; BORDER-TOP: gray 1px solid; BORDER-LEFT: gray 1px solid; CURSOR: hand; BORDER-BOTTOM: gray 1px solid;"; 
  //按钮默认文本 
  private const string _BUTTONDEFAULTTEXT = "..."; 
  private System.Web.UI.WebControls.Calendar _Calendar; 
  public override ControlCollection Controls 
  { 
   get 
   { 
    EnsureChildControls(); //确认子控件集都已被创建 
    return base.Controls; 
   } 
  } 
  //创建子控件(服务器日历控件) 
  protected override void CreateChildControls() 
  { 
   Controls.Clear(); 
   _Calendar = new Calendar(); 
   _Calendar.ID = MyCalendarID; 
   _Calendar.SelectedDate = DateTime.Parse(Text); 
   _Calendar.TitleFormat = TitleFormat.MonthYear; 
   _Calendar.NextPrevFormat = NextPrevFormat.ShortMonth; 
   _Calendar.CellSpacing = 0; 
   _Calendar.Font.Size = FontUnit.Parse("9pt"); 
   _Calendar.Font.Name = "Verdana"; 
   _Calendar.SelectedDayStyle.BackColor = ColorTranslator.FromHtml("#333399"); 
   _Calendar.SelectedDayStyle.ForeColor = ColorTranslator.FromHtml("White"); 
   _Calendar.DayStyle.BackColor = ColorTranslator.FromHtml("#CCCCCC"); 
   _Calendar.TodayDayStyle.BackColor = ColorTranslator.FromHtml("#999999"); 
   _Calendar.TodayDayStyle.ForeColor = ColorTranslator.FromHtml("Aqua"); 
   _Calendar.DayHeaderStyle.Font.Size = FontUnit.Parse("8pt"); 
   _Calendar.DayHeaderStyle.Font.Bold = true; 
   _Calendar.DayHeaderStyle.Height = Unit.Parse("8pt"); 
   _Calendar.DayHeaderStyle.ForeColor = ColorTranslator.FromHtml("#333333"); 
   _Calendar.NextPrevStyle.Font.Size = FontUnit.Parse("8pt"); 
   _Calendar.NextPrevStyle.Font.Bold = true; 
   _Calendar.NextPrevStyle.ForeColor = ColorTranslator.FromHtml("White"); 
   _Calendar.TitleStyle.Font.Size = FontUnit.Parse("12pt"); 
   _Calendar.TitleStyle.Font.Bold = true; 
   _Calendar.TitleStyle.Height = Unit.Parse("12pt"); 
   _Calendar.TitleStyle.ForeColor = ColorTranslator.FromHtml("White"); 
   _Calendar.TitleStyle.BackColor = ColorTranslator.FromHtml("#333399"); 
   _Calendar.OtherMonthDayStyle.ForeColor = ColorTranslator.FromHtml("#999999"); 
   _Calendar.NextPrevFormat = NextPrevFormat.CustomText; 
   _Calendar.NextMonthText = "下月"; 
   _Calendar.PrevMonthText = "上月"; 
   _Calendar.Style.Add("display","none"); //默认不显示下拉日历控件 
   _Calendar.SelectionChanged += new EventHandler(_Calendar_SelectionChanged); 
   this.Controls.Add(_Calendar); 
  } 
  [ 
   Category("Appearance"), //该属性所属类别,参见图 
   DefaultValue(""), //属性默认值 
   Description("设置该日期控件的值。") //属性的描述 
  ] 
  public string Text 
  { 
   get 
   { 
    EnsureChildControls(); 
    return (ViewState["Text"] == null)?System.DateTime.Today.ToString("yyyy-MM-dd"):ViewState["Text"].ToString(); 
   } 
   set 
   { 
    EnsureChildControls(); 
    DateTime dt = System.DateTime.Today; 
    try 
    { 
     dt = DateTime.Parse(value); 
    } 
    catch 
    { 
     throw new ArgumentOutOfRangeException("请输入日期型字符串(例如:1981-04-29)!"); 
    } 
    ViewState["Text"] = DateFormat == CalendarEnum.LongDateTime?dt.ToString("yyyy-MM-dd"):dt.ToString("yyyy-M-d"); 
   } 
  } 
  //重载服务器控件的Enabled属性,将选择日期按钮变灰(禁用) 
  public override bool Enabled 
  { 
   get 
   { 
    EnsureChildControls(); 
    return ViewState["Enabled"] == null?true:(bool)ViewState["Enabled"]; 
   } 
   set 
   { 
    EnsureChildControls(); 
    ViewState["Enabled"] = value; 
   } 
  } 
  public string ButtonStyle 
  { 
   get 
   { 
    EnsureChildControls(); 
    object o = ViewState["ButtonSytle"]; 
    return (o == null)?_BUTTONDEFAULTSTYLE:o.ToString(); 
   } 
   set 
   { 
    EnsureChildControls(); 
    ViewState["ButtonSytle"] = value; 
   } 
  } 
  [ 
   DefaultValue(CalendarEnum.LongDateTime), 
  ] 
  public CalendarEnum DateFormat 
  { 
   get 
   { 
    EnsureChildControls(); 
    object format = ViewState["DateFormat"]; 
    return format == null?CalendarEnum.LongDateTime:(CalendarEnum)format; 
   } 
   set 
   { 
    EnsureChildControls(); 
    ViewState["DateFormat"] = value; 
    DateTime dt = DateTime.Parse(Text); 
    Text=DateFormat == CalendarEnum.LongDateTime?dt.ToString("yyyy-MM-dd"):dt.ToString("yyyy-M-d"); 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string MyCalendarID //复合控件ID 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.ClientID+"_MyCalendar"; 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string MyCalendarName //复合控件名称 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.UniqueID+":MyCalendar"; 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string DatePickerInputID //复合控件中输入框的ID 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.ClientID+"_DateInput"; 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string DatePickerInputName //复合控件中输入框的名称 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.UniqueID+":DateInput"; 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string DatePickerButtonID //复合控件中按钮的ID 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.ClientID+"_DateButton"; 
   } 
  } 
  [ 
   Browsable(false), 
   DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 
  ] 
  public string DatePickerButtonName //复合控件中按钮的名称 
  { 
   get 
   { 
    EnsureChildControls(); 
    return this.UniqueID+":DateButton"; 
   } 
  } 
  public string ButtonText 
  { 
   get 
   { 
    EnsureChildControls(); 
    return ViewState["ButtonText"] == null?_BUTTONDEFAULTTEXT:(string)ViewState["ButtonText"]; 
   } 
   set 
   { 
    EnsureChildControls(); 
    ViewState["ButtonText"] = value; 
   } 
  } 
  /// 
  /// 将此控件呈现给指定的输出参数。 
  /// 
  /// 
  protected override void Render(HtmlTextWriter output) 
  { 
   //在页面中输出控件时,产生一个表格(二行二列),以下是表格的样式 
   output.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0"); 
   output.AddAttribute(HtmlTextWriterAttribute.Border, "0"); 
   output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0"); 
   output.AddStyleAttribute("LEFT", this.Style["LEFT"]); 
   output.AddStyleAttribute("TOP", this.Style["TOP"]); 
   output.AddStyleAttribute("POSITION", "absolute"); 
   if (Width != Unit.Empty) 
   { 
    output.AddStyleAttribute(HtmlTextWriterStyle.Width, Width.ToString()); 
   } 
   else 
   { 
    output.AddStyleAttribute(HtmlTextWriterStyle.Width, "200px"); 
   } 
   output.RenderBeginTag(HtmlTextWriterTag.Table); //输出表格 
   output.RenderBeginTag(HtmlTextWriterTag.Tr); //表格第一行 
   output.AddAttribute(HtmlTextWriterAttribute.Width, "90%"); 
   output.RenderBeginTag(HtmlTextWriterTag.Td); 
   //以下是第一行第一列中文本框的属性及其样式设置 
   if (!Enabled) 
   { 
    output.AddAttribute(HtmlTextWriterAttribute.ReadOnly, "true"); 
   } 
   output.AddAttribute(HtmlTextWriterAttribute.Type, "Text"); 
   output.AddAttribute(HtmlTextWriterAttribute.Id, DatePickerInputID); 
   output.AddAttribute(HtmlTextWriterAttribute.Name, DatePickerInputName); 
   output.AddAttribute(HtmlTextWriterAttribute.Value, Text); 
   output.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%"); 
   output.AddStyleAttribute(HtmlTextWriterStyle.Height, "100%"); 
   output.AddStyleAttribute(HtmlTextWriterStyle.FontFamily, Font.Name); 
   output.AddStyleAttribute(HtmlTextWriterStyle.FontSize, Font.Size.ToString()); 
   output.AddStyleAttribute(HtmlTextWriterStyle.FontWeight, Font.Bold?"bold":""); 
   output.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, ColorTranslator.ToHtml(BackColor)); 
   output.AddStyleAttribute(HtmlTextWriterStyle.Color, ColorTranslator.ToHtml(ForeColor)); 
   output.RenderBeginTag(HtmlTextWriterTag.Input); //输出文本框 
   output.RenderEndTag(); 
   output.RenderEndTag(); 
   output.AddAttribute(HtmlTextWriterAttribute.Width, "*"); 
   output.RenderBeginTag(HtmlTextWriterTag.Td); 
   //以下是第一行第二列中按钮的属性及其样式设置 
   if (!Enabled) 
   { 
    output.AddAttribute(HtmlTextWriterAttribute.Disabled, "true"); 
   } 
   output.AddAttribute(HtmlTextWriterAttribute.Type, "Submit"); 
   output.AddAttribute(HtmlTextWriterAttribute.Id, DatePickerButtonID); 
   output.AddAttribute(HtmlTextWriterAttribute.Name, DatePickerButtonName); 
   output.AddAttribute(HtmlTextWriterAttribute.Value, ButtonText); 
   output.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%"); 
   output.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.GetPostBackEventReference(this)); //点击按钮时需要回传服务器来触发后面的OnClick事件 
   output.AddAttribute(HtmlTextWriterAttribute.Style, ButtonStyle); 
   output.RenderBeginTag(HtmlTextWriterTag.Input); //输出按钮 
   output.RenderEndTag(); 
   output.RenderEndTag(); 
   output.RenderEndTag(); 
   output.RenderBeginTag(HtmlTextWriterTag.Tr); 
   output.AddAttribute(HtmlTextWriterAttribute.Colspan, "2"); 
   output.RenderBeginTag(HtmlTextWriterTag.Td); 
   _Calendar.RenderControl(output); //将日历子控件输出 
   output.RenderEndTag(); 
   output.RenderEndTag(); 
   output.RenderEndTag(); 
  } 
  //复合控件必须继承IpostBackEventHandler接口,才能继承RaisePostBackEvent事件 
  public void RaisePostBackEvent(string eventArgument) 
  { 
   OnClick(EventArgs.Empty); 
  } 
  protected virtual void OnClick(EventArgs e) 
  { 
   //点击选择日期按钮时,如果日历子控件没有显示则显示出来并将文本框的值赋值给日历子控件 
   if (_Calendar.Attributes["display"] != "") 
   { 
    _Calendar.SelectedDate = DateTime.Parse(Text); 
    _Calendar.Style.Add("display",""); 
   } 
  } 
  //复合控件中的日历控件日期变化事件 
  private void _Calendar_SelectionChanged(object sender, EventArgs e) 
  { 
   //当选择的日期变化时,将所选日期赋值给文本框并将日历子控件隐藏 
   Text = _Calendar.SelectedDate.ToString(); 
   _Calendar.Style.Add("display","none"); 
  } 
 } 
} 
  在上面的代码中,需要注意以下几点: 
  ·如果你想将此控件的某些属性供重载,则在声明属性前加上virtual关键字; 
  ·在页面输出此控件时(即在Render事件中),是先定义子控件的样式或属性,然后再产生子控件; 
  ·在隐藏日历子控件时,建议不要使用Visible属性来显示/隐藏,使用Visible=false隐藏时服务器端将不会将日历控件HTML代码发送给客户端,会导致复合控件装载日历控件的表格会空白一块出来,影响页面的布局。所以使用样式display=none设置来使日历控件在客户端隐藏,但是HTML代码依然存在于页面中; 
  四、结束语 
  在编写服务器控件时,需要一定的HTML语言基础,也要清楚.NET程序的请求处理方式。服务器控件封装了客户端行为及逻辑判断,无需开发者添加更多代码。当然,有些地方使用服务器控件可以带来方便,但是也增加了服务器的负荷。有时适当的结合JavaScript使一些代码在客户端运行,可提高WEB应用程序效率			
 
  
 
 
  
