庖丁解牛第3章:从零开始开发服务器控件

Render方法在设计模式和运行模式下都执行。

Render呈现控件的几种方式:

A、使用HtmlTextWriter类输出
 protected override void RenderContents(HtmlTextWriter output)
 {
  output.AddAttribute(HtmlTextWriterAttribute.Href,"http://www.cnblogs.com/");
  output.AddAttribute(HtmlTextWriterAttribute.Target, "blank");
  output.AddStyleAttribute(HtmlTextWriterStyle.Color, "Blue");
  output.AddStyleAttribute(HtmlTextWriterStyle.Cursor, "Hand");
  output.RenderBeginTag(HtmlTextWriterTag.A);
  output.Write(this.Text);
  output.RenderEndTag();
  output.WriteBreak();
 }

 Output.AddAttribute方法生成控件的属性,它有许多重载方法,可以直接以字符串形式把属性名称和属性值写入到输出流,也可以使用HtmlTextWriterAttribute枚举,帮助输入控件属性:
  output.AddAttribute(HtmlTextWriterAttribute.Bgcolor, "#6699ff");
  output.AddAttribute("bgcolor", "#6699ff");
  output.AddAttribute(HtmlTextWriterAttribute.Checked, "true");
  output.AddAttribute("checked", "true");
  output.AddAttribute(HtmlTextWriterAttribute.Class, "TextBoxStyleName");
  output.AddAttribute("class", "TextBoxStyleName");
  output.AddAttribute(HtmlTextWriterAttribute.Onclick, "alert('Hello');");
  output.AddAttribute("onclick", "alert('Hello');");
  output.AddAttribute(HtmlTextWriterAttribute.ReadOnly, "true");
  output.AddAttribute("readonly", "true");
 
 Output.AddStyleAttribute方法生成控件的样式属性,像style="width:100%;" 中的width就是样式属性标记。
 同样使用HtmlTextWriterStyle枚举也可以帮助快速输入样式属性标记。
 
 RenderBeginTag和枚举HtmlTextWriterTag:
  output.RenderBeginTag(HtmlTextWriterTag.A);
  output.RenderBeginTag("A");
  output.RenderBeginTag(HtmlTextWriterTag.Button);
  output.RenderBeginTag("button");
  output.RenderBeginTag(HtmlTextWriterTag.Div);
  output.RenderBeginTag("div");
  output.RenderBeginTag(HtmlTextWriterTag.Table);
  output.RenderBeginTag("table");
  output.RenderBeginTag(HtmlTextWriterTag.Input);
  output.RenderBeginTag("input");

 在实际开发中建议尽量使用HtmlTextWriterAttribute,HtmlTextWriterStyle和HtmlTextWriterTag枚举生成控件以及其属性标记,使用这些枚举输出最大的好处是我们不用关心浏览器的兼容性,让它在Render时自行处理,否则我们必须得保证当前浏览器要支持此标记。
 
B、直接输出HTML标签 
 protected override void RenderContents(HtmlTextWriter output)
 {
  output.Write("<a href='http://www.csdn.net' target='blank' style='color: Blue;cursor:Hand;'>");
  output.Write(this.Text);
  output.Write("</a>");
 }
 
 注意:
 1、当要连续输出多个HTML标记时,调用多个Write方法把标记直接输出到输出流中要比先组装好字符串再一次性输出到输出流中效率要高
 2、强烈建议不要把A和B的方式混用,这样做除了代码比较混乱,不便于阅读外,还有一个重要的原因:
 protected override void RenderContents(HtmlTextWriter output)
 {
  output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
  output.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
  output.AddAttribute(HtmlTextWriterAttribute.Border, "0");
  output.RenderBeginTag(HtmlTextWriterTag.Table);
  output.Write("<TR><TD>我是单元格内容</TD></TR>");
  output.Write("</TABLE");          
 }
 最后最出会多一个</table>标签,这是由于HtmlTextWriter的方法输出控件标记时比较智能,output.RenderEndTag可以省略,运行环境在运行时会自动检测到默认的尾签并自动追加。

 

C、使用服务器控件的RenderControl方法
 protected override void RenderContents(HtmlTextWriter output)
 {
  HtmlGenericControl A = new HtmlGenericControl("A");
  A.Attributes.Add("href", " http://blog.csdn.net/ChengKing");
  A.Attributes.Add("target", "blank");
  A.Style.Add(HtmlTextWriterStyle.Color, "Blue");
  A.Style.Add(HtmlTextWriterStyle.Cursor, "Hand");
  A.InnerText = this.Text;
  A.RenderControl(output);           
 }

区别:
 使用C输出方式其效率要比A和B的直接输出HTML标记要低些,因为它在控件解析时把服务端控件解析成HTML标记是要花些时间的,但是在很多场景下,比如创建复合控件时不得不用此方法
 
 采用A和B方式的缺点是,代码已经写死,以至于它不能自动识别浏览器,这样就不能根据浏览器类型生成对应的能够被各个浏览器识别的代码,那么做成的控件有可能在有些浏览器上不能够正常呈现或功能受限等。
 反之,使用C方式输出即可以实现大部分跨浏览器的不同代码。

 另外,使用Write方法最致命的缺点是服务器无法识别它(它不包含于服务器的控件集合中,对于保存页面状态的视图状态ViewState来说,里面也不会保存这些控件的信息),即使设置了name属性。
 也就是说Write一般用于直接输出文本,或者输出简单HTML标签,前提是这些标签不处理服务端事件或处理服务端回发数据。

 
CreateChildControls方法:
 CreateChildControls方法一般用于创建组合控件。在此方法中可以定义自己需要的控件,进行实例化和赋值等,并将其添加到当前Controls集合中。
 protected override void CreateChildControls()
 {
  Controls.Clear();   
  Button button = new Button();
  button.ID = "btnOK"; 
  button.CommandName = "ButtonClick";
  this.Controls.Add(_button);

  TextBox textBox = new TextBox();
  textbox.ID = "tbText";
  this.Controls.Add(textBox);
 }
 重写从Control继承的受保护的CreateChildControls方法,以创建子控件的实例,并将它们添加到Controls集合,此方法可能会在页面和控件的生命周期内反复调用。
 为避免控件重复,ChildControlsCreated属性通常被设置为true。如果此属性返回true,则CreateChildControls方法会立即退出。
 

INamingContainer接口:
 INamingContainer是一个没有任何方法的接口。当用控件实现此接口时,ASP.NET 页框架将在此控件下创建新的命名范围。这样可以保证子控件在控件层次结构树中具有唯一的ID。INamingContainer的作用就是解决一个页面中使用多个自定义控件的ID命名冲突问题。

 

EnsureChildControls方法:
 该方法用于确定服务器控件是否包含子控件。如果不包含,则创建子控件。该方法首先检查ChildControlsCreated属性的当前值。如果此值为false,则调用CreateChildControls方法。当需要确保已创建子控件时,将调用该方法。

 

CompositeControl类:
 复合控件类要派生自System.Web.UI.WebControls.CompositeControl类。CompositeControl类是一个抽象类,该类可为自定义控件提供命名容器和控件设计器。CompositeControl类继承自WebControl基类,并且实现INamingContainer和ICompositeControlDesignerAccessor接口。

 

RecreateChildControls方法:
 重写ICompositeControlDesignerAccessor接口的RecreateChildContrls方法,它的基类虚实现代码如下:
 protected virtual void RecreateChildControls()
 {
  base.ChildControlsCreated = false;
  this.EnsureChildControls();
 }
 其功能主要是间接调用了CreateChildControls方法。这样在设计模式下,就可以执行创建子控件的方法并呈现创建的子控件。因为默认在设计模式情况下CreateChildControls方法是不执行的。


 

PS:附控件完整代码:

 

自定义输入框
 [DefaultProperty("TextBoxValue")]  //指定组件的默认属性
    [ToolboxData("<{0}:Field runat=server></{0}:Field>")]  //指定生成的默认标记
    public class Field : CompositeControl
    {
        
#region 子控件
        
private Label lb;
        
private TextBox tb;
        
private RegularExpressionValidator rev;  //验证控件
        #endregion

        
#region 属性
        [Category(
"LabelTextBox")]  //属性标签名
        [Description("标签显示信息")]  //属性描述
        public string LabelTitle
        {
            
get
            {
                
//该方法用于确定服务器控件是否包含子控件。如果不包含,则创建子控件。
                this.EnsureChildControls();  
                
return this.lb.Text;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.lb.Text = value;
            }
        }

        [Category(
"LabelTextBox")]
        [Description(
"文本框显示文本")]
        
public string TextBoxValue
        {
            
get
            {
                
this.EnsureChildControls();
                
return tb.Text;
            }
            
set
            {
                
this.EnsureChildControls();
                tb.Text 
= value;
            }
        }

        
//在表示宽度或高度等属性时一般建议使用Unit类型,而不建议使用int,string等类型
        
//这种类型的功能远没有Unit强大,Unit可以支持整型、字符串、百分比、点、像素等值类型
        [Category("LabelTextBox")]
        [Description(
"标签宽度")]
        
public Unit LabelWidth
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.lb.Width;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.lb.Width = value;
            }
        }

        [Category(
"LabelTextBox")]
        [Description(
"标签高度")]
        
public Unit LabelHeight
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.lb.Height;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.lb.Height = value;
            }
        }

        
private Unit unitTextBoxWidth = Unit.Empty;
        [Category(
"LabelTextBox")]
        [Description(
"文本框宽度")]
        
public Unit TextBoxWidth
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.tb.Width;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.tb.Width = value;
            }
        }

        
private Unit unitTextBoxHeight = Unit.Empty;
        [Category(
"LabelTextBox")]
        [Description(
"文本框宽度")]
        
public Unit TextBoxHeight
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.tb.Height;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.tb.Height = value;
            }
        }

        [Category(
"LabelTextBox")]
        [Description(
"验证表达式")]
        
public string ValidateExpression
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.rev.ValidationExpression;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.rev.ValidationExpression = value;
            }
        }

        [Category(
"LabelTextBox")]
        [Description(
"错误提示")]
        
public string ErrorMessage
        {
            
get
            {
                
this.EnsureChildControls();
                
return this.rev.ErrorMessage;
            }
            
set
            {
                
this.EnsureChildControls();
                
this.rev.ErrorMessage = value;
            }
        }
        
#endregion

        
#region CreateChildControls
        
/// <summary>
        
/// 注意方法一开始调用this.Controls.Clear();语句先清空集合,防止重复增加相同的子控件。
        
/// 在方法最后这句this.ChildControlsCreated = true;指定已经创建了子控件集合,
        
/// 当调用this.EnsureChildControls(每个属性的set和get语句中都调用此方法)这样的方法时,可以先判断this.ControlControlsCreated值是否为false,如果为false才创建控件。
        
/// 像CreateChildControls这样的方法不属于页面生命周期的某一阶段,在程序中任意阶段都有可能被开发人员调用,因此使用此方法要非常谨慎,也要考虑效率。
        
/// </summary>
        protected override void CreateChildControls()
        {
            
this.Controls.Clear();

            lb 
= new Label();
            
this.lb.ID = "lbTitle";
            
this.lb.Width = Unit.Pixel(50);
            
this.Controls.Add(lb);

            tb 
= new TextBox();
            
this.tb.ID = "tbValue";
            
this.tb.Width = Unit.Pixel(100);
            
this.Controls.Add(tb);


            rev 
= new RegularExpressionValidator();
            
this.rev.ID = "revValidator";
            
this.rev.ControlToValidate = this.tb.ID;
            
this.rev.ErrorMessage = "[输入格式不正确!]";
            
this.rev.Display = ValidatorDisplay.Static;
            
this.Controls.Add(this.rev);

            
this.ChildControlsCreated = true;
        }
        
/// <summary>
        
///  重写此方法,在设计模式下就可以执行创建子控件的方法并呈现创建的子控件。
        
/// </summary>
        protected override void RecreateChildControls()
        {
            
if (this.ChildControlsCreated == false)
            {
                
base.RecreateChildControls();
            }
        }
        
#endregion

        
#region Render
        
/// <summary>
        
/// 面页布局Render,配合CreateChildControls使用
        
/// </summary>
        
/// <param name="writer"></param>
        protected override void Render(HtmlTextWriter writer)
        {
            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, 
"0");
            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, 
"0");
            writer.AddAttribute(HtmlTextWriterAttribute.Border, 
"0");

            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);

            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            
this.lb.RenderControl(writer);
            writer.RenderEndTag();
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            
this.tb.RenderControl(writer);
            writer.RenderEndTag();
            
//通过if条件语句主要是控制在运行模式下提高执行效率,在设计模式下可以不用验证;
            
//另外,这样也可以保持在设计模式下表格布局统一都为两列,开发人员更容易布局。
            if (String.IsNullOrEmpty(this.ValidateExpression) == false && this.DesignMode == false)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                
this.rev.RenderControl(writer);
                writer.RenderEndTag();
            }

            writer.RenderEndTag();
            writer.RenderEndTag();
        }
        
#endregion
    }

 

aspx:

 <cc1:Field id="CompositeControl1_1" runat="server"
         LabelTitle="邮件" ValidateExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.] \w+)*"
        ErrorMessage="请输入正确的Email!" >

posted @ 2010-07-26 17:45  DaCHun  阅读(330)  评论(0编辑  收藏  举报