{
public enum ButtonType
{
Submit, Button, Reset
}
public Button()
{
Attributes = new Dictionary < string , string > ();
}
public string TagName { get ; set ; }
public string ID { get ; set ; }
public string Text { get ; set ; }
public string Name { get ; set ; }
public IDictionary < string , string > Attributes { get ; set ; }
public ButtonType Type { get ; set ; }
public void AddAttribute( string name, string value)
{
Attributes.Add(name, value);
}
private string GetAttributeHtml()
{
var html = string .Empty;
foreach (var item in Attributes)
{
html += " " + item.Key + " =\ "" + item.Value + " \ "" ;
}
return html;
}
public string Render()
{
return String.Format( " <{0} type=\ " { 1 }\ " id=\ " { 2 }\ " name=\ " { 3 }\ " value=\ " { 4 }\ " {5} /> " , TagName, Type, ID, Name, Text, GetAttributeHtml());
}
}
OK,如何?简版Button完成,可以输出基本的Button标签。先不说性能如何,您觉得能用吗?微软会这么写吗?! 显然不会,这太简陋了,而且也没考虑到面向对象,因为标签都具备公共特性,比如 TagName,id ,name,class等,那我们改良下。
{
public interface IControl
{
void Render();
}
public class Control
{
public string ID { get ; set ; }
public string Name { get ; set ; }
public string TagName { get ; set ; }
public string TagEndHtml { get ; set ; }
public ControlAttributes Attributes { get ; set ; }
}
public class ControlAttributes
{
private readonly IDictionary < string , string > _attributes;
public ControlAttributes()
{
_attributes = new Dictionary < string , string > ();
}
public void AddAttribute( string name, string value)
{
_attributes.Add(name, value);
}
public string GetAttributeHtml()
{
var html = string .Empty;
foreach (var item in _attributes)
{
html += " " + item.Key + " =\ "" + item.Value + " \ "" ;
}
return html;
}
}
public class Button : Control, IControl
{
public enum ButtonType
{
Submit, Button, Reset
}
public Button()
{
TagEndHtml = " /> " ;
}
public string Text { get ; set ; }
public ButtonType Type { get ; set ; }
public void Render()
{
HttpContext.Current.Response.Write(String.Format( " <{0} type=\ " { 1 }\ " id=\ " { 2 }\ " name=\ " { 3 }\ " value=\ " { 4 }\ " {5} {6} " , TagName, Type, ID, Name, Text, Attributes.GetAttributeHtml(),TagEndHtml));
}
}
}
似乎有点感觉了,提取了公共部分,也抽象了Control,还把Attribute单独设计成类(职责单一嘛),但是,要想弄一个真正周全的Button,远没有这么简单,来一起看看微软官方的实现吧:)
首先看继承关系:
public class WebControl : Control, IAttributeAccessor
public class Control : IComponent, IDisposable, IParserAccessor, IUrlResolutionService, IDataBindingsAccessor, IControlBuilderAccessor, IControlDesignerAccessor, IExpressionsAccessor
所有的控件都是Control,所以需要一个Control基类,而微软又把控件分为了服务端控件(WebControl)和Html控件,所以所有的服务端控件又继承与WebControl。
我们知道Html控件就是我们常用的Html标签加上 runat="server"。所以功能方面要比WebControl差的远,自然就分开了。
我们来看看这些类,Control有个Render方法虚方法,这个设计是合理的,有些控件不一定非要自己实现输出,所以设计成接口不合理。
{
this .RenderChildren(writer);
}
//而内容也很简洁,就是输出子控件。
protected internal virtual void RenderChildren(HtmlTextWriter writer)
{
ICollection children = this ._controls;
this .RenderChildrenInternal(writer, children);
}
internal void RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
{
if (( this .RareFields != null ) && ( this .RareFields.RenderMethod != null ))
{
writer.BeginRender();
this .RareFields.RenderMethod(writer, this );
writer.EndRender();
}
else if (children != null )
{
foreach (Control control in children)
{
control.RenderControl(writer);
}
}
}
如果是特殊的控件就输出开始 内容和结束,如果不是则调用子控件的所有输出。这些方法都是虚方法,可以被子类覆盖。而我们需要关注的是一个类HtmlTextWriter。
是的,所有的输出都没离开他不是吗?而且Control类没有这个类型成员,神了!是外部调用传进来的。 他到底干什么的?
{
public const char DoubleQuoteChar = ' " ' ;
public const string EndTagLeftChars = " </ " ;
public const char EqualsChar = ' = ' ;
public const string EqualsDoubleQuoteString = " =\ "" ;
private int indentLevel;
public const string SelfClosingChars = " / " ;
public const string SelfClosingTagEnd = " /> " ;
public const char SemicolonChar = ' ; ' ;
public const char SingleQuoteChar = ' \ ' ;
public const char SlashChar = ' / ' ;
public const char SpaceChar = ' ' ;
public const char StyleEqualsChar = ' : ' ;
private bool tabsPending;
private string tabString;
public const char TagLeftChar = ' < ' ;
public const char TagRightChar = ' > ' ;
protected HtmlTextWriterTag TagKey
{
get
{
return this ._tagKey;
}
set
{
this ._tagIndex = ( int ) value;
if (( this ._tagIndex < 0 ) || ( this ._tagIndex >= _tagNameLookupArray.Length))
{
throw new ArgumentOutOfRangeException( " value " );
}
this ._tagKey = value;
if (value != HtmlTextWriterTag.Unknown)
{
this ._tagName = _tagNameLookupArray[ this ._tagIndex].name;
}
}
}
}
public enum HtmlTextWriterTag
{
Unknown,
A,
Acronym,
Address,
Area,
B,
Base,
Basefont,
Bdo,
......................
}
{
RenderAttribute attribute;
if ( this ._attrList == null )
{
this ._attrList = new RenderAttribute[ 20 ];
}
else if ( this ._attrCount >= this ._attrList.Length)
{
RenderAttribute[] destinationArray = new RenderAttribute[ this ._attrList.Length * 2 ];
Array.Copy( this ._attrList, destinationArray, this ._attrList.Length);
this ._attrList = destinationArray;
}
attribute.name = name;
attribute.value = value;
attribute.key = key;
attribute.encode = encode;
attribute.isUrl = isUrl;
this ._attrList[ this ._attrCount] = attribute;
this ._attrCount ++ ;
}
微软把Attribute也设计成了类,而且他没有用Dictionary<T,T>,而是RenderAttribute数组。有意思的是默认20个长度,太多还要搞个双倍的数组重新copy过去。所以大家不要搞太多的属性哦:)
他们把所有的标签(HtmlTextWriterTag、属性(HtmlTextWriterAttribute)和样式属性(HtmlTextWriterStyle)都设计成了枚举,这样比较好,枚举使代码易读而且要比字符串处理更快,这些名称也很少有变化。
{
this .RenderBeginTag(writer);
this .RenderContents(writer);
this .RenderEndTag(writer);
}
public virtual void RenderBeginTag(HtmlTextWriter writer)
{
this .AddAttributesToRender(writer);
HtmlTextWriterTag tagKey = this .TagKey;
if (tagKey != HtmlTextWriterTag.Unknown)
{
writer.RenderBeginTag(tagKey);
}
else
{
writer.RenderBeginTag( this .TagName);
}
}
protected internal virtual void RenderContents(HtmlTextWriter writer)
{
base .Render(writer);
}
public virtual void RenderEndTag(HtmlTextWriter writer)
{
writer.RenderEndTag();
}
方法够简洁,开始->内容->结束。其中开始和结束调用的都是writer的方法,而内容则是base(即Control)的Render方法,上面已经贴了。
回头看Render调用的是RenderControl
{
this .RenderControl(writer, this .Adapter);
}
Button控件在输出的时候是没有Contents的,所以我们看到他只是重写了RenderContents方法,方法体就是空。
LinkButton是<a>连接,中间就得有text,所以他的方法就是输出Text了。
而CheckBox类则是重写的Render方法!因为CheckBox生成的代码有点特殊 他包含了<label>和<input> 默认的Render方法三步骤不适合他,所以必须重写Render。你可能会问DropDownList应该也重写的Render方法吧,因为他有<select>和<option>,其实。。 他谁都没重写?Why?因为他是<dorpdownlist><item>这种形式,也就是父控件包含子控件,而且都遵循3步骤,所以默认就OK了。
说完RenderContent,再说前后两个步骤RenderBeginTag和RenderEndTag。
{
this .TagKey = tagKey;
bool flag = true ;
if ( this ._isDescendant)
{
flag = this .OnTagRender( this ._tagName, this ._tagKey);
this .FilterAttributes();
string str = this .RenderBeforeTag();
if (str != null )
{
if ( this .tabsPending)
{
this .OutputTabs();
}
this .writer.Write(str);
}
}
TagInformation information = _tagNameLookupArray[ this ._tagIndex];
TagType tagType = information.tagType;
bool flag2 = flag && (tagType != TagType.NonClosing);
string endTag = flag2 ? information.closingTag : null ;
if (flag)
{
if ( this .tabsPending)
{
this .OutputTabs();
}
this .writer.Write( ' < ' ); // 这是输出标签开始
this .writer.Write( this ._tagName); // 标签名称
string str3 = null ;
for ( int i = 0 ; i < this ._attrCount; i ++ ) // 杯具的属性循环
{
RenderAttribute attribute = this ._attrList[i];
if (attribute.key == HtmlTextWriterAttribute.Style)
{
str3 = attribute.value;
}
else
{
this .writer.Write( ' ' );
this .writer.Write(attribute.name);
if (attribute.value != null )
{
this .writer.Write( " =\ "" );
string url = attribute.value;
if (attribute.isUrl && ((attribute.key != HtmlTextWriterAttribute.Href) || ! url.StartsWith( " javascript: " , StringComparison.Ordinal)))
{
url = this .EncodeUrl(url);
}
if (attribute.encode)
{
this .WriteHtmlAttributeEncode(url);
}
else
{
this .writer.Write(url);
}
this .writer.Write( ' " ' );
}
}
}
if (( this ._styleCount > 0 ) || (str3 != null )) // 杯具的样式输出
{
this .writer.Write( ' ' );
this .writer.Write( " style " );
this .writer.Write( " =\ "" );
CssTextWriter.WriteAttributes( this .writer, this ._styleList, this ._styleCount); // 还有专门的CssWriter 继续杯具的循环样式
if (str3 != null )
{
this .writer.Write(str3);
}
this .writer.Write( ' " ' );
}
if (tagType == TagType.NonClosing) // 判断怎么关。。
{
this .writer.Write( " /> " );
}
else
{
this .writer.Write( ' > ' );
}
}
string str5 = this .RenderBeforeContent();
if (str5 != null )
{
if ( this .tabsPending)
{
this .OutputTabs();
}
this .writer.Write(str5);
}
if (flag2)
{
if (tagType == TagType.Inline)
{
this ._inlineCount ++ ;
}
else
{
this .WriteLine();
this .Indent ++ ;
}
if (endTag == null )
{
endTag = " </ " + this ._tagName + ' > ' .ToString(CultureInfo.InvariantCulture);
}
}
if ( this ._isDescendant)
{
string str6 = this .RenderAfterTag();
if (str6 != null )
{
endTag = (endTag == null ) ? str6 : (str6 + endTag);
}
string str7 = this .RenderAfterContent();
if (str7 != null )
{
endTag = (endTag == null ) ? str7 : (str7 + endTag);
}
}
this .PushEndTag(endTag);
this ._attrCount = 0 ;
this ._styleCount = 0 ;
}
ok,今天说了下Render,呵呵,其实大都是在说类的设计。弄清Freamwork的同时,我们也来领会微软的设计。至于具体的自定义控件开发大家可以搜索下,会有很多的文章。无外乎就是AddAttribtue和Render。