您的位置:首页 > 其它

自定义服务器控件(扩展现有 Web 控件)

2012-12-05 17:52 155 查看
       很多情况下,你并不需要从头开始创建一个新控件。有些功能也许在 ASP.NET 的 Web 控件的基本集合中已经存在了。所有这些控件都是普通类,你可以组合它们(使用其他类的实例来创建一个新类)或者继承它们(扩展一个现存类和改变它的功能来创建一个新类)

      

组合控件

       ASP.NET 提供了这样一个功能,即允许你从其他现存的 Web 控件构建你的控件类。

       基本的技巧是从类 System.Web.UI.WebControls.CompositeControl (它自身继承 WebControl)派生一个新类。然后,必须覆盖 CreateChildControls()方法来添加子控件。此时,你可以创建一个或多个控件对象,设置它们的属性和事件处理程序,最后把它们加入到当前控件的 Controls 集合中。这种方式最大的优点在于你根本不需要定制呈现代码,呈现工作被委托给了作为组成部件的服务器控件,你也不需要担心触发回传和获取回传数据等细节,因为子控件自身会处理这些细节

 

       下面创建一个 TitledTextBox 控件,这个控件组合一个 Lable 和 一个 TextBox:

[code]public class TitledTextBox : CompositeControl


{ ... }

[/code]

       CompositeControl 控件实现了 INamingContainer 接口。这只是个标记接口,没有任何方法,它的作用仅仅是告诉 ASP.NET 保证所有的子控件拥有唯一的 ID 值。(ASP.NET 通过在控件的 ID 之前附加服务器控件的 ID 来实现这一点,这确保了不会有任何命名冲突,即使你的页面拥有不止一个 TitledTextBox 控件实例)。

 

       为了简单起见,应该使用成员变量跟踪构成控件。这使得控件里的任何方法都能方便的访问成员控件。但此时还不应该创建这些控件:

[code]protected Label label;


protected TextBox textBox;

[/code]

 

       网页无法直接访问这些控件中的任何一个。如果允许访问某个属性,在控件类中必须添加属性:

[code]public string Title


{


get { return (string)ViewState["Title"]; }


set { ViewState["Title"] = value; }


}


 


public string Text


{


get { return (string)ViewState["Text"]; }


set { ViewState["Text"] = value; }


}

[/code]

       注意:这些属性只是在视图状态里存储信息,而不是直接访问子控件。这是因为子控件也许还不存在。这些属性将会在 CreateChildControls()方法里被应用到子控件上。

       所有的控件都在 <span> 标签里被呈现,这样做很有效。这保证了如果网页针对 TitledTextBox 控件使用了 font、color、position 等特性时,所有的子控件都能实现预期的效果。

 

       现在,可以创建子控件对象了。这些对象都独立于另外一个控件对象:LiteralControl ,LiteralControl 仅表现为一小段 HTML 代码,本例中 LiteralControl 包装了两个不换行空格:

[code]protected override void CreateChildControls()


{


// Add the label.


label = new Label();


label.EnableViewState = false;


label.Text = Title;


Controls.Add(label);


 


// Add a space


Controls.Add(new LiteralControl("  "));


 


// Add the text box.


textBox = new TextBox();


textBox.EnableViewState = false;


textBox.Text = Text;


textBox.TextChanged += new EventHandler(OnTextChanged);


Controls.Add(textBox);


}


 


public event EventHandler TextChanged;


 


protected virtual void OnTextChanged(object sender, EventArgs e)


{


if (TextChanged != null)


{


TextChanged(this, e);


}


}

[/code]

       查看下测试效果:





       你可能会更喜欢使用 HtmlTextWriter 完全控制 HTML 的呈现。但是,如果你想处理回传、事件以及创建复杂控件(如 GridView,或者导航助手),那么使用组合控件能极大化的简化工作。

 

为 TitledTextBox 提供更好的设计时支持

       对于这个示例还有一个值得添加的细节。如果在 CreateChildControls()方法已经被调用来呈现控件之后修改了 Title 或者 Text 属性,一定需要确保子控件被重新生成。

       比如在按钮回传的事件中修改了这 2 个属性后,回传结束,页面呈现时,仍能看见旧的属性值。这是因为 CreateChildControls()方法优先于按钮事件执行,因此控件属性的值还是比较旧的 ViewState 中保存的值。此时按钮事件执行,对控件的 ViewState 赋予了新的值,因此这种变化需要再次回传后才能发生。这并不是我们期待的效果和反应!

       一定要有一种机制确保页面呈现后,用户修改了控件属性时,重新创建子控件并更新所有的值。

       下面这段代码可以示例出这种机制(以 Title 举例):

[code]public string Title


{


get { return (string)ViewState["Title"]; }


set


{


ViewState["Title"] = value;


 


// 赋值时,如果控件已创建,那么控件沿用了上一次视图状态的值


// 因此需要重新创建子控件


if (this.ChildControlsCreated)


{


this.RecreateChildControls();


}


}


}

[/code]

 

派生控件

       派生控件指从现存的控件类派生出一个更加专用的控件。你可以覆盖或增加你需要的功能,而不需要重新创建整个控件,你可以省去很多工作。不过,此方式并非总是可行的,因为某些控件在私有方法里编写了一些基础设施的关键代码,你无法覆盖

       有时,你可创建一个派生控件以便能用某些样式或格式化属性预先初始化一个现存的控件。例如,你可以创建一个用于在 OnInit()方法里设置样式的自定义控件 Calendar 或 GridView,这样,当添加这些控件时,该实例就已经是你所期望的样式格式化了。

 

为特定数据创建标签

       创建自定义控件的一个普遍原因:为某些特定类型的数据而微调控件。

       例如,标准的 Label 是一个灵活通用的工具,可以用来呈现文本内容并插入任意的 HTML 。然而,有时更需要一种能照顾到某些编码方式的更高级的文本输出方式。

       Xml 控件,它允许用一个 XSLT 样式表在页面里显示 XML 内容。然而,XML 控件并未给出不使用 XSLT 样式表先将其转换来显示任意的 XML 的方式。因此,加入你想复制 IE 的行为,该怎么做呢?

       IE 显示了一个彩色字符编码的 XML 标签的树形结构。你可以用 XSLT 样式表实现这种方法。然而,另一个有趣的选择是专门为 XML 内容创建一个自定义的 Label 控件,而这个 Label 控件可以自动应用你所需的格式。

 

示例介绍

       首先,如果你不采取任何步骤,而试着显示 XML 内容,所有的 XML 标签都会被识别为无效的 HTML 标签,因此不会被显示,结果只会显示一个杂乱无章的代表所有元素内容的文本块:





 

       可以通过 Server.HtmlEncode() 方法来稍微改善,但仍差强人意!所有的空白都被折叠了,所有的换行都被忽略了,导致了一个不可读的长字符串:



 

 

自定义 XmlLabel 控件

       自定义 XmlLabel 控件通过在 XML 的开始和结束标签上应用格式化而解决了这个问题。这个功能被封装到一个名为 ConvertXmlTextToHtmlText()的静态方法中。之所以被实现为静态方法而非实例方法,就是为了能从其他控件中调用它来格式化文本并展示

       ConvertXmlTextToHtmlText()使用下面的正则表达式来查找字符串中所有 XML 标签:


<([^>]+)> // 匹配小于号与大于号,及中间一系列非大于号的字符,遇到大于号匹配查找立即结束。



注意

       正则表达式使用的是所谓的贪婪匹配(greedy matching),这意味着总是尽可能多的进行匹配。因此这个简单的 <.+> 表达式不可用。它将匹配文档中第一个小于号和文档末尾的大于号之间的所有内容。换言之,结果将只有一个匹配,这个匹配将所有内嵌的匹配都混为一谈。

 

       一旦找到了一个匹配,下一步就是用你想要的文本替换这个匹配文本。替换表达式如下:


<<b>$1</b>>



       用 HTML 实体字符来替换掉小于号和大于号。中间的文本用加粗来格式化。$1 是一个反向引用,它引用搜索表达式中用括号括住的文本。

 

       一旦标签显示为粗体,最后一步就是用   字符实体替换字符串中空格,用 <br /> 替换所有换行。下面是完整的代码:

[code]public class XmlLabel : Label


{


public static string ConvertXmlTextToHtmlText(string inputText)


{


string startPattern = "<([^>]+)>";


Regex regEx = new Regex(startPattern);


string outputText = regEx.Replace(inputText, "<<b>$1</b>>");


 


outputText = outputText.Replace(" ", " ");


outputText = outputText.Replace("\r\n", "<br />");




return outputText;


}


 


protected override void RenderContents(HtmlTextWriter output)


{


string xmlText = ConvertXmlTextToHtmlText(Text);


output.Write(xmlText);


}


}

[/code]

       这里没有调用 RenderContents()的基本实现。这是因为 XmlLabel 控件的目标是替代标签文本的呈现逻辑,而非补充!





       在这个框架的基础上,可以做很多工作来完善它,包括彩色编码和自动缩进。

       也可以使用类似的技巧创建标签(Label),自动把邮件地址和 URL 转换为链接(以 <a> 标签包装),格式化多行文本成无序列表等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: