第4章 庖丁解牛系列—服务器控件属性
本章内容
4.1 控件属性的作用
4.2 简单属性
4.3 属性的设计时特性
4.4 复杂属性
4.5 深入研究——定制自己的属性编辑器
4.6 类型转换器
4.7 实现自定义属性
提醒: 由于本文章内容比较多, 乃至把文本编辑器几乎崩溃掉, 弄到零晨3点也没能把文中的图片弄上, 稍幸上传成功到这里一个, 请大家讲就访问这里吧: http://blog.csdn.net/ChengKing/archive/2009/01/01/3678774.aspx
4.1 控件属性的作用
|
|
4-1 Webcontrol类属性窗口
4.1.1 系统属性
当开发控件时如果选择基类,比如选择继承WebControl基类,一旦继承于此类,一些默认的系统属性就会成为当前控件的属性集的一部分,图4-1所示的是WebControl类的系统属性。
可以看到一个通用Web控件所应具备的基本属性都已经有了,在实际开发控件时选择某个基类。
4.1.2 自定义属性
4.1.1节所讲的是系统已有的属性,在开发控件时一般都要为自己的控件增加一些自定义属性。自定义属性与系统属性完全一样。只是由于不具有系统属性的通用性而需要开发者自己去实现。下面看一下属性的语法格式:
string strText = "默认值";
public string Text
{
get
{
return strText;
}
set
{
strText = value;
}
}
以上是一个最简单的属性,由一个set和get语段组成。注意,set和get段不是必需的,比如可以去掉set段表示此属性只允许读取而不允许接收值。
事实上属性的特性范畴还比较多,如简单属性、复杂属性,以及属性在设计时的特性和标记形式的格式等,下面将对这些特性一一进行介绍。
4.2 简单属性
简单属性是类型为字符串的或容易转换为字符串的属性。简单属性在控件的开始标记上自行保留为属性。.NET Framework 类库中的基元值类型,如String、Boolean、Int16、Int32、DateTime、Byte、Char、Double和Enum均为简单属性。可以通过添加代码将简单属性存储在ViewState字典中,以便在回发间进行状态管理。请看例子:
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
public string Value
{
get
{
String s = (String)ViewState["Value"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["Value"] = value;
}
}
上面声明的简单属性中,属性可接收及其返回值的类型是String,表示本属性为简单属性。另外,简单属性可以直接使用ViewState存储其值,因为简单属性可以直接映射为字符串,而ViewState中可以直接接收的格式也是字符串。
ViewState是页面视图状态机制中的存储机制,是为解决在Web浏览器两次访问之间无状态保持而提供的一种机制,视图信息存储在网页中专用HiddenField区域,而且每次页面提交都会往返于客户端和服务器,因此一般视图主要用于存储少量的文本数据信息,而不适合存储数据量比较大的业务数据。另外,复杂属性的存储也要自己实现视图机制功能,这一点在后面讨论视图机制的章节会详细介绍,这里仅作了解即可。
只要控件中定义了上面的代码段,对应在页面设计器属性窗口中就会包含此项,如图4-2所示。
图4-2 属性窗口中的属性
在属性窗口中输入一些文本,打开设计器中的源代码会看到如下标记的ASP.NET代码:
<cc1:controlproperty >
</cc1:controlproperty>
同样,Boolean、Int16、Int32、DateTime、Byte、Char、Double和Enum等类型的属性与上面的String类型属性代码标记完全一样。简单属性比较简单,就讲解到这里。
4.3 属性的设计时特性
.NET Framework为 控件设计时属性提供了很多丰富的类,这些属性的功能非常灵活,控制范围广泛,比如可以控制该属性在属性窗口中的显示模式,如:是否在属性窗口中显示该属 性,也可以指定此属性必须接收值类型描述,按组分类等,也可以控制文本的标记呈现格式等,甚至可以自己定义一个属性类,实现自己想实现的功能。下面讲一下 常用的.NET Framework的属性类对控件的支持功能。
Ø Bindable
指定属性是否可以绑定一个有效数据源,通常使用布尔值进行设置。例如:Bindable(true)。如果使用值true标记属性,表示该属性可以绑定一个有效数据源。
Ø Browsable
指定属性是否应该在属性窗口中显示,使用布尔值设置。一般情况下,对于常用的和比较重要的属性设置Browsable为true,否则,设置Browsable为false。
Ø EditorBrowsable
设置属性在编辑器中的可见性,比如设置在智能提示列表不显示或高级用户才可以看到该属性。
Ø Category
指定属性在属性浏览器中进行分组显示的类别。该设计时特性帮助可视化编辑器将属性进行逻辑分组。通常分为:外观(Appearance)、行为(Behavior)、布局(Layout)、数据(Data)、操作(Action)、键盘(Key)和鼠标(Mouse)等。如果您安装的是中文版的IDE,则默认情况下中文分类和英文分类是通用的,即设置成“数据”或“Data”类别是等价的。
Ø Description
设置显示在属性窗口最下面的描述属性功能的文字说明。
Ø DesignOnly
如果此属性设置为true,表示该属性只能在设计期间使用,不能在页面代码中设置其值。
Ø ReadOnly
设置该属性是否为只读状态。如果此特性设置为true,则在属性窗口能看到属性,但不能设置其值。另外,通过在属性语句体中把 set 语句段去掉也可以起到相同的效果。
Ø Themeable
设置该属性是否支持主题特性,默认情况下属性都支持主题。当该属性与界面无关时可以设置其值为false,禁用该属性的主题功能。
Ø DesignerSerializationVisibility
指定属性是否以及如何在代码中序列化,其值为DesignerSerializationVisibility的枚举值,存在3种设置方式:
— DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)指定序列化程序不应该序列化属性值;
— DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)指定应该允许序列化程序序列化属性的值;
— DesignerSerializationVisibility(DesignerSerializationVisibility.Content)指定序列化程序应该序列化属性的内容,而不是属性本身。此字段为只读。Visible为其默认值。
这里说的序列化是指在IDE中的设计器界面切换到代码视图时,看到的代码标记,或反向切换时把代码标记转化到设计器界面。后面讲复杂属性时会通过示例介绍此属性功能。
Ø NotifyParentProperty
指示当此设计特性应用到的属性的值被修改时将通知其父属性。换言之,如果属性的父属性应该在该属性值被修改时接到通知,则向该属性应用NotifyParentProperty特性。通常使用布尔值进行设置。一般常用于复杂属性,通知转换器更新到父级标记。
Ø ParseChildren
使用该设计特性指示当在页面上以声明方式使用控件时,嵌套在服务器控件标记内的XML元素是应该视为属性还是应视为子控件。通常情况下,包含两种声明方式:
— ParseChildren(true)表示将子XML元素作为服务器控件的属性分析;
— ParseChildren(bool childrenasProperty, string defaultProperty),其中childrenasProperty和上面的方式中的布尔值参数意义相同,defaultProperty定义默认情况下将子控件分析为服务器控件的集合属性。
Ø PersistChildren
该设计特性指示设计时是否应将服务器控件的子控件作为内部嵌套控件保持。如果该特性为PersistChildren(true),则将服务器控件的子控件作为嵌套服务器控件标记保持。如果为PersistChildren(false),则将该控件的属性作为嵌套元素保持。
Ø PersistenceMode
指定如何将服务器控件属性或事件保持到ASP.NET页面的元数据属性,共存在4种枚举设置方式:
— PersistenceMode(PersistenceMode.Attribute)指定属性或事件保持为属性;
— PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)指定属性作为服务器控件的唯一内部文本,如果属性值是HTML编码的,只能对字符串作这种指定;
— PersistenceMode(PersistenceMode.InnerDefaultProperty)指定属性在服务器控件中保持为内部文本,还指示将该属性定义为元素的默认属性,只能指定一个属性为默认属性;
— PersistenceMode(PersistenceMode.InnerProperty)指定属性在服务器控件中保持为嵌套标记,通常用于复杂对象,它们具有自己的持久性属性。
关于以上4种标记的具体用法,下一节会详细介绍。
Ø DefaultValue
指定属性的默认值。此特性的设置需要特别谨慎,假如设置的值不为空,则开发人员在使用时如果自己输入的值与默认值相同,则控件不会装载开发人员输入的值。也就是说此默认值不能指定为具有有效意义或业务意义的实际值。一般设置为空即可。
Ø DisplayName
指定在属性窗口中显示的别名。此别名仅在属性窗口中看到,当转换器转换到代码视图,以及在页面后面的代码中编码还是以实际的属性名称为准,而不是以该别名为准。
Ø ParenthesizedPropertyName
指定属性在属性窗口中显示时,是否带有括号,相当于在Category分组特性基础上的对属性窗口属性集的排序功能,如果不带括号该属性会自动排在该组的前面。
Ø PasswordPropertyText
指定是否设置成密码文本。如果设置为true,则在属性窗口中输入的文本会用特定的密码符号显示,而不是显示原文本;另外,在代码视图中看到的仍为原文本。
Ø TypeConverter
指定用作此特性所绑定到的对象的转换器的类型。用于转换的类必须从TypeConverter继承。使用ConverterTypeName属性来获取为该特性所绑定到的对象提供数据转换的类名。后面会通过代码示例讲解如何自定义一个自己的类型转换器。
Ø Editor
指定该属性的编辑器,如系统的文件编辑器、文本编辑器、颜色编辑器,还有集合编辑器等,也可以自己实现编辑器,具体用法后面会讲到。
Ø ToolBoxItem
此属性为类特性。属于工具箱属性,可以设置当前控件是否在工具箱中显示,以及所在工具箱项的类型名称等信息。默认生成的控件都显示在工具箱中。
Ø ToolBoxData
此特性为类特性,即不是属性的特性,而是类的特性,设置位置也是在类的上面。ToolBoxData表示从工具箱中拖一个控件到设计界面上时默认显示标记格式,如:
[ToolboxData("<{0}:ControlProperty runat=server></{0}:ControlProperty>")]
可以修改参数字符串,定制为自己想要的格式,但要保证所添加的属性为有意义的属性。
Ø DefaultProperty
此特性为类特性。它指定服务器控件的默认属性,例如:[DefaultProperty("Text")]。
指定用黑色粗体显示默认属性特性的属性名称。一般设置比较重要或常用的属性为默认的属性。如TextBox控件的Text属性。
Ø DefaultEvent
此特性为类特性。指定服务器控件的默认事件,例如:[DefaultEvent("OnClicked")]。
指定用黑色粗体显示默认事件特性的事件名称。一般设置比较重要或常用的属性为默认的事件,如Button控件的OnClick事件。
Ø ValidationProperty
此特性为类特性,指定该控件的哪个属性作为验证属性。当该控件与验证控件组合使用时,验证控件会自动验证该特性指定的属性。
Ø AspNetHostingPermission
此属性为JIT编译时代码访问安全属性。需要使用此属性确保链接到控件的代码具有适当的安全权限。Control类带有两个JIT编译时代码访问安全属性标记:
AspNetHostingPermission(SecurityAction.Demand,Level=AspNetHostingPermissionLevel.Minimal)和AspNetHostingPermission(SecurityAction.InheritanceDemand,Level=AspNetHosting PermissionLevel.Minimal).在使用时应把第一个属性应用于当前开发的控件,第二个属性是可选的,因为继承请求是可传递的,在派生类中仍有效。
Ø ControlBuilder
分析时特性,将自定义控件生成器与控件关联。只有在您希望使用自定义控件生成器,对页分析器用分析控件的声明性语法的默认逻辑进行修改时,才需要应用此特性。如果仅希望指定控件标记中的内容是否与属性或子控件对应,请使用ParseChildrenAttribute,而不要使用自定义控件生成器。
Ø Designer
设计时特性,指定与控件关联的设计器类。控件设计器类用于控制关联的控件在可视化设计器的设计图面上的外观和行为。
还有一些更复杂的,包括在设计模式下的元数据属性类在这里没有列出,因为在后面有专门的章节详细介绍,通过代码示例更容易理解。在这里只要理解上面这些属性类功能,开发一般的控件是没有问题了。
4.4 复杂属性
4.4.1 概述
复杂属性是属性的类型不是简单值类型,或者是一个包含其他属性的类。例如.NET Framework中的Style,Font,Point等都是复杂属性。另外还有集合属性,这里也将它作为复杂属性归类,对于集合属性在本章后面会单独拿出来一节进行详细讲解。
4.4.2 复杂属性的几种标记形式
先看看一个典型的代码段:
<asp:GridView ID="GridView1" runat="server">
<FooterStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />
<RowStyle BackColor="#EFF3FB" />
<PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign= "Center" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="true" ForeColor= "#333333" />
<HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />
<EditRowStyle BackColor="#2461BF" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
<asp:ListBox ID="ListBox1" runat="server">
<asp:ListItem Value="1">男</asp:ListItem>
<asp:ListItem Value="0">女</asp:ListItem>
</asp:ListBox>
代码非常简单,一段是GridView控件的一些属性,另一段是ListBox控件的一些属性。仔细观察一下这些控件的属性标记,我们能很容易给它们归类,比如:GridView的ID/Runat属性标记类型相似,FootStyle/RowStyle这样的标记类似,还有Font-Bold这样的属性标记,ListBox的集合项ListItem标记也比较特殊等这么多标记类型。我们在开发控件时当然也希望能够生成这么多灵活的标记类型,那么本节就详细介绍一下服务端控件的这些标记类型是怎样生成的。
开始之前,有必要说明一下,下面所有代码示例在调试时都是在设计模式下进行的。关于在设计模式下如何调试代码在第2章已经详细讲解过了,如果读者还有疑问请再回顾一下第2章的内容。
通常情况下,复杂属性表现为几种形式:连字符形式属性、内部嵌套形式属性和内部嵌套形式默认属性。下面将介绍以上几种形式复杂属性的具体实现方法。
4.4.2.1 连字符形式的复杂属性标记
连字符复杂属性标记是指属性通过“复杂属性名称-复杂属性的子属性名称”的格式追加到主控件的标记形式。下面用一个例子来讲解这种标记。
首先,定义一个复合类Person,结构如下:
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
public class Person
{
private string strName;
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get { return strName; }
set { strName = value; }
}
private int intAge;
/// <summary>
/// 年龄
/// </summary>
public int Age
{
get { return intAge; }
set { intAge = value; }
}
}
u 再在控件中增加一个类型为Person的属性,将以下代码增加到控件中:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- private Person pPerson;
- [Description("复杂属性")]
- [Category("复杂属性")]
- public Person Person
- {
- get
- {
- if (pPerson == null)
- {
- pPerson = new Person();
- }
- return pPerson;
- }
- }
此属性与简单属性的区别有两点:第一,属性接收和返回的类型不是简单类型(int,string 等),而是用我们自己定义的Person类;第二,复杂属性一般没有set语句,因为一般是对复杂属性的子属性(或子对象)赋值,只要保证它的子属性(子对象)中具有get/set语句即可。编译此控件,在IDE中打开页面,并打开控件的属性窗口,会看到如图4-3所示的界面。
图4-3 复杂属性
|
|
解决办法是,为主控件属性Person增加PersistenceMode和DesignerSerializationVisibility两个设计特性,片段代码如下所示:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- … …
- [PersistenceMode(PersistenceMode.Attribute)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- public Person Person
- {
- … …
- }
1
.PersistenceMode特性PersistenceMode特性指定在页面*.aspx或*.ascx文件中如何保持复杂属性,理解此特性用法非常重要,这里详细介绍一下它的用法。PersistenceMode有四种枚举状态:
Ø PersistenceMode.Attribute
表示复杂属性的标记作为主控件的属性,如果复杂属性包含子属性,则子属性持久化成破折号连接的样式,比如:
<asp:GridView ID="GridView1" runat="server">
<HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />
</asp:GridView>
上面代码中的Font-Bold对于<HeaderStyle>来说就是使用了PersistenceMode下的Attribute枚举标记类型。本节例子中就是实现此标记形式。
Ø PersistenceMode.InnerProperty
表示用属性名称作为嵌套标签表示复杂属性,比如GridView的HeaderStyle属性,就是使用了PersistenceMode下的InnerProperty标记形式。代码如下:
<asp:GridView ID="GridView1" runat="server">
<HeaderStyle BackColor="#507CD1" Font-Bold="true" ForeColor="White" />
</asp:GridView>
Ø PersistenceMode.InnerDefaultProperty
该特性值与InnerProperty类似,都是在主控件外标记复杂属性。不同的是,InnerDefaultProperty不需要像InnerProperty那样把属性名作为最外标签,一般用于常用的或重要复杂属性或集合,如:
<asp:ListBox ID="ListBox1" runat="server">
<asp:ListItem Value="1">男</asp:ListItem>
<asp:ListItem Value="0">女</asp:ListItem>
</asp:ListBox>
以上代码中的ListItem,它的特点是直接把ListItem单项放到ListBox的标记内部,而没有增加一个类似<Items>的标记在ListItem的外面。另外,InnerDefaultProperty在一个控件类中只能设置一个复杂属性,而InnerProperty可以设置任意多个复杂属性。
一般情况下会把最重要的一个集合属性设置为InnerDefaultProperty枚举标记类型。
Ø PersistenceMode.EncodedInnerDefaultProperty
在上面的代码中ListItem.Text属性(值为“男”或“女”),除了标记方式与InnerDefaultProperty有点区别外,其内容会进行HTML编码,比如把HTML标记<div>编码为<div>;,即要保证其内容不能再存储HTML标记和子标签。
2.DesignerSerializationVisibility特性
此特性表示指定在设计时序列化复杂对象的方式。它有三个枚举类型:
Ø DesignerSerializationVisibility.Visible
表示代码生成器要对属性本身生成代码。
Ø DesignerSerializationVisibility.Hidden
表示代码生成器不对属性生成代码,即在属性窗口设置的值不会被代码生成器生成到*.aspx或*.ascx文件中。
Ø DesignerSerializationVisibility.Content
表示代码生成器生成复杂属性内容的代码,而不是其本身。比如在上面的People类中,我们实际要操作的数据是People类下面的 Name/Sex/Age属性,即我们在属性窗口中修改了Name/Sex/Age的值后,会仅把这些值通过代码生成器映射到*.aspx或*.axcx页面中。
如果没有设置DesignerSerializationVisibility特性,则其值默认为DesignerSerialization Visibility. Visible;一般复杂属性都要设置为DesignerSerializationVisibility.Content。
理解了PersistenceMode 和DesignerSerializationVisibility两个特性的用法,我们再继续完成上面进行中的代码部分。为属性Person增加了这两个特性后,再打开Person类定义代码,为该类增加一个类特性TypeConverter,如下所示:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [TypeConverter(typeof(ExpandableObjectConverter))]
- public class Person
- {
- … …
- }
TypeConverter
特性指定转换器的类型,ExpandableObjectConverter表示可扩展对象与其他类型的转换器类,该类为系统提供。另外,也可以自己定义转换器规则类,本章后面会有专门介绍。增加以上属性之后,编译控件再查看属性窗口,就可以在属性窗口中进行设置Person属性的值了,如图4-5所示。
图4-5 设置Person属性值
在属性浏览器中为Person设置值,后切换到代码视图,会看到如下标记:
<cc1:controlproperty ></cc1:controlproperty>
到此我们就实现以上功能:连字符复杂属性的标记形式。
4.4.2.2 内部嵌套复杂属性标记
连字符复杂属性标记虽然能够实现复杂属性,且代码生成器能够进行正/反向转换,但它把所有复杂属性都挤到主控件的属性上,显示比较臃肿,设想一下,如果GridView把它的<HeadStyle><Rowstyle>等属性都挤到GridView主标记内部,会是什么样子,为了解决这个问题,下面我们就实现一个类似以下代码中的RowStyle标记形式的复杂属性。
<asp:GridView ID="GridView1" runat="server">
<RowStyle BackColor="#EFF3FB" />
</asp:GridView>
u 在控件所在项目中增加一个类文件 RowStyle.cs,定义其内容如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [TypeConverter(typeof(ExpandableObjectConverter))]
- public class RowStyle //TableItemStyle: Table的Row和Cell样式基类,也可以直
- //接继承此类
- {
- private Color bcBackColor;
- [NotifyParentProperty(true)]
- public Color BackColor
- {
- get { return bcBackColor; }
- set { bcBackColor = value; }
- }
- }
注意不要漏掉
TypeConverter和NotifyParentProperty,其用途在前面中已经讲过了。再在主控件中增加一个RowStyle类型的属性,属性名为RowStyle,增加后的属性代码片段如下所示:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [PersistenceMode(PersistenceMode.InnerProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [NotifyParentProperty(true)]
- [Category("复杂属性")]
- [Description("复杂属性——内部嵌套形式")]
- public RowStyle RowStyle
- {
- get
- {
- if (rsRowStyle == null)
- {
- rsRowStyle = new RowStyle();
- }
- return rsRowStyle;
- }
- }
选择
PersistenceMode特性的InnerProperty枚举项,表示生成嵌套标记;至于DesignerSerializationVisibility特性,依然选择Content枚举值,这里也是对复杂属性RowStyle的类对象子属性进行序列化。如还不清楚这两个属性的使用,请到前面的4.1.1节回顾一下。然后,在主控件加两个类特性,如下所示:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [ParseChildren(true), PersistChildren(false)] //继承WebControl时可以省略此行
- public class ControlProperty : WebControl
- {
- … …
- }
PerseChildren
特性指定页面分析器把控件标记中的内容解析为属性还是子控件,该属性值设置为true,则表示解析为属性。PersistChildren指定设计器把控件标记中的内容保存为属性还是子控件,该属性值设置为false,表示保存为属性。设置了如上几个重要特性后,编译控件,在设计器属性窗口中设置RowStyle属性值,并切换到代码视图,会看到RowStyle的标记形式如下所示:
<cc1:controlproperty >
<RowStyle BackColor="CornflowerBlue" />
</cc1:controlproperty>
只要实现RowStyle复杂类型,那么类似GridView的其他嵌套属性如:<HeaderStyle>,<FooterStyle>,<SelectedRowStyle>,<EditRowStyle>等实现方法用同样方式也可以实现。
在嵌套标记属性比较多的情况下,这些属性看起来效果比上节讲过的连字符复杂属性标记要清晰许多。
另外,还可以按上面所说的步骤对集合类型生成类似的内部嵌套默认属性,如:
<asp:DropDownList >
<Items>
<asp:ListItem Value="red">红色</asp:ListItem>
<asp:ListItem Value="green">绿色</asp:ListItem>
<Items>
</asp:DropDownList>
基于实现原理与RowStyle类似,且本章后面有专门章节详细探讨集合属性,这里不作代码示范。集合属性也是非常重要和常用的复杂属性类型。
4.4.2.3 内部嵌套默认复杂属性标记
内部嵌套默认属性与内部嵌套属性非常类似,一般用于设置某个控件的集合属性。比如标准服务器控件中的DropDownList控件中的属性均为内部嵌套默认属性,代码如下:
<asp:DropDownList >
<asp:ListItem Value="red">红色</asp:ListItem>
<asp:ListItem Value="green">绿色</asp:ListItem>
</asp:DropDownList>
内部嵌套默认属性的ListItem标记外部没有像内部集合属性一样嵌套在<Items></Items>中,然后把<Items>嵌套在主控件标记中,而是直接把<asp:listItem></asp:listItem>嵌套在主控件标记内部,一般当该控件只有一个集合复杂属性的情况时使用;而当一个集合中有多个集合或复杂属性时一般设置为内部嵌套复杂属性标记形式。
为主控件增加集合属性之前,先要建立两个类:
Ø ListItem类:集合中的单项定义类。
Ø Items类:集合类,提供ListItem的容器以及一些常用的添加/删除等子项方法。
1.ListItem类完整代码
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [ToolboxItem(false)]
- [TypeConverter(typeof(ExpandableObjectConverter))]
- public class ListItem : Control
- {
- private string _Text;
- private string _Value;
- public ListItem()
- { }
- public ListItem(string strText,string strValue)
- {
- this._Text = strText;
- this._Value = strValue;
- }
- /// <summary>
- /// 文本属性
- /// </summary>
- [NotifyParentProperty(true)]
- public string Text
- {
- get { return _Text; }
- set { _Text = value; }
- }
- /// <summary>
- /// 值属性
- /// </summary>
- [NotifyParentProperty(true)]
- public string Value
- {
- get { return _Value; }
- set { _Value = value; }
- }
- }
此子项类的代码比较简单
,唯一要说明的是上面的[ToolBoxItem(false)]表示不在IDE工具箱的控件集合中显示。很显然这不是一个控件,不能在工具箱集合列表中显示,一般除了主控件之外的其余类都要把ToolBoxItem类元数据特性置为false,否则当使用者拖一个不完整的控件标记到页面上时可能出现控件不能使用的情况。2.Items类的完整代码
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 菜单实现类[实用泛型集合]
- /// </summary>
- [
- ToolboxItem(false),
- ParseChildren(true)
- ]
- public class Items : List<ListItem>
- {
- #region 定义构造函数
- public Items()
- : base()
- {
- }
- #endregion
- /// <summary>
- /// 得到集合元素的个数
- /// </summary>
- public new int Count
- {
- get
- {
- return base.Count;
- }
- }
- /// <summary>
- /// 表示集合是否为只读
- /// </summary>
- public bool IsReadOnly
- {
- get
- {
- return false;
- }
- }
- /// <summary>
- /// 添加对象到集合
- /// </summary>
- /// <param name="item"></param>
- public new void Add(ListItem item)
- {
- base.Add(item);
- }
- /// <summary>
- /// 清空集合
- /// </summary>
- public new void Clear()
- {
- base.Clear();
- }
- /// <summary>
- /// 判断集合中是否包含元素
- /// </summary>
- /// <param name="item"></param>
- /// <returns></returns>
- public new bool Contains(ListItem item)
- {
- return base.Contains(item);
- }
- /// <summary>
- /// 移除一个对象
- /// </summary>
- /// <param name="item"></param>
- /// <returns></returns>
- public new bool Remove(ListItem item)
- {
- return base.Remove(item);
- }
- /// <summary>
- /// 设置或取得集合索引项
- /// </summary>
- /// <param name="index"></param>
- /// <returns></returns>
- public new ListItem this[int index]
- {
- get
- {
- return base[index];
- }
- set
- {
- base[index] = value;
- }
- }
- }
这里的
Items采用泛型集合,继承list<T>强类型集合作为基类,此外在System.Collections. Generic命名空间中还有其他一些强类型集合。增加完上面两个类后,实现内部默认集合属性,还需要设置两个类设计特性:一是在控件类前设置ParseChildren(true,“默认属性名称”),指定主控件中的属性名称表示是属性,而不是子控件,ParseChildren在4.3节已经做了讲解;二是设置[PersistChildren(false)]类特性,表示要把集合标记作为属性方式保持和进行序列化。
在主控件的集合属性前要设置如下三个特性:
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[NotifyParentProperty(true)]
第一个特性,指定集合属性为内部默认属性;第二个特性,指定要序列化的是集合属性的内容,而不是集合属性本身;第三个特性,指定集合属性的子属性修改时会通知父属性。
新建一个Web自定义控件文件,并按以上所述进行设置,控件主类核心代码如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 控件主类[复杂属性-内部默认属性]
- /// </summary>
- [DefaultProperty("Text")]
- [ToolboxData("<{0}:CollectionControlProperty runat=server></{0}:Collection ControlProperty>")]
- [PersistChildren(false)]
- [ParseChildren(true, "Items")]
- public class CollectionControlProperty : WebControl
- {
- private Items items;
- [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [NotifyParentProperty(true)]
- [TypeConverter(typeof(CollectionConverter))]
- [Category("复杂属性")]
- [Description("复杂属性——内部默认嵌套形式")]
- public Items Items
- {
- get
- {
- if (this.items == null)
- {
- this.items = new Items();
- }
- return this.items;
- }
- }
- … …
- }
除了设置上面所提到的属性外,本集合类还多了一个特性
[TypeConverter(typeof(collection Converter)]。此特性指定本集合属性转换到代码视图时采用系统默认的集合转换器。针对常用的类型,系统提供了一组默认的转换器,后面章节会介绍怎样创建自定义复杂类型的类型转换器。经过以上设置后,在页面上拖动一个控件,并在属性窗口中增加填加几个子项,如图4-6所示。
图4-6 集合编辑器
设置完后,回到源代码视图,会看到刚才设置好的几个子项:
<cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">
<cc1:ListItem ID="ListItem1" runat="server" Text="红色" Value="red">
</cc1:ListItem>
<cc1:ListItem ID="ListItem2" runat="server" Text="蓝色" Value="blue">
</cc1:ListItem>
<cc1:ListItem ID="ListItem3" runat="server" Text="绿色" Value="green">
</cc1:ListItem>
</cc1:CollectionControlProperty>
本节主要是完成一个复杂集合属性,并把集合属性设置为默认属性。本节示例控件的所有源代码请参阅随书光盘中的内容。
4.4.2.4 内部嵌套编码默认属性
请看下面这段我们经常使用的代码:
<asp:DropDownList >
<asp:ListItem Value="red">红色</asp:ListItem>
<asp:ListItem Value="green">绿色</asp:ListItem>
</asp:DropDownList>
细心的读者可能看到,表示Text(“红色”位置的属性)的属性不像Value属性是附属于ListItem标记,而是在两个<asp:ListItem></asp:ListItem>标记之间呈现。这样的标记主要用于显示非HTML标记或非子控件的纯文本,本节主要完成这种格式属性的实现。
为了保留前面控件已有的功能,重新定义两个类Item2和ListItem2。
1.Items集合类
代码如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 菜单实现类[实用泛型集合]
- /// </summary>
- [
- ToolboxItem(false),
- ParseChildren(true)
- ]
- public class Items2 : List<ListItem2>
- {
- //省略,此集合类内部代码与Items完全相同
- }
2.ListItem2子项类
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 菜单实现类[实用泛型集合]
- /// </summary>
- [
- ToolboxItem(false),
- ParseChildren(true)
- ]
- public class Items2 : List<ListItem2>
- {
- //省略,此集合类内部代码与Items完全相同
- }
代码如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 子项类
- /// </summary>
- [ToolboxItem(false)]
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [ParseChildren(true, "Text")]
- [PersistChildren(false)]
- public class ListItem2 : Control
- {
- private string _Text;
- private string _Value;
- public ListItem2()
- { }
- public ListItem2(string strText, string strValue)
- {
- this._Text = strText;
- this._Value = strValue;
- }
- /// <summary>
- /// 文本属性
- /// </summary>
- [NotifyParentProperty(true)]
- [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
- [Description("复杂属性——内部默认嵌套形式")]
- public string Text
- {
- get { return _Text; }
- set { _Text = value; }
- }
- /// <summary>
- /// 值属性
- /// </summary>
- [NotifyParentProperty(true)]
- public string Value
- {
- get { return _Value; }
- set { _Value = value; }
- }
- }
此子项类要做一些特性设置,类元特性需要增加两个特殊的特性:
Ø [ParseChildren(true, "Text")]
Ø [PersistChildren(false)]
第一个特性表示将子Text元素作为服务器控件的属性分析;第二个特性表示将该控件的属性作为嵌套元素保持。
另外,还要注意要对作为编码内部属性的属性进行设置,比如这里为Text属性加上:
[PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
u 进行如上设置后,增加一个主控件文件,并进行如下所示设置:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [DefaultProperty("Text")]
- [ToolboxData("<{0}:EncodedInnerDefaultPropertyControl runat=server></{0}: EncodedInnerDefaultPropertyControl>")]
- [PersistChildren(false)]
- [ParseChildren(true, "Items")]
- public class EncodedInnerDefaultPropertyControl : WebControl
- {
- public EncodedInnerDefaultPropertyControl()
- {
- }
- private Items2 items;
- [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility. Content)]
- [NotifyParentProperty(true)]
- [TypeConverter(typeof(CollectionConverter))]
- [Category("复杂属性")]
- [Description("复杂属性——内部默认嵌套形式")]
- public Items2 Items
- {
- get
- {
- if (this.items == null)
- {
- this.items = new Items2();
- }
- return this.items;
- }
- }
- … …
- }
上面主控件类与
4.4.2.3中主控件类设置完全相同,这里就不再作说明。设置完成后编译控件库,拖动此控件到页面中,可以看到在属性窗口增加了几个集合项,如图4-7所示。
图4-7 属性窗口
集合设置界面与4.4.2.3节中的完全相同,但切换到代码视图界面,会发现序列化后的代码变化了,如下所示:
<cc1:EncodedInnerDefaultPropertyControl ID="EncodedInnerDefaultProperty Control1" runat="server" Items-Capacity="4">
<cc2:ListItem2 ID="ListItem22" runat="server" Value="red">红色</cc2:ListItem2>
<cc2:ListItem2 ID="ListItem23" runat="server" Value="blue">蓝色</cc2:ListItem2>
</cc1:EncodedInnerDefaultPropertyControl>
可以看到Text属性已经不再作为ListItem的直接属性,而是嵌套在<ListItem2> </ListItem2>之间。
本节主要说明控件内部嵌套编码默认属性格式的实现。在实现时需要注意的是,对ListItem2子项类进行一些元数据特性设置,因为Text是属于ListItem2类的属性。
4.4.3 深入研究——复杂属性分析器
4.4.3.1 使用AddParsedSubObject控制复杂内容(子控件)
在4.4.2节已经把各种各样的复杂属性类型都实现了,这些都是在实际开发中常用的属性格式,能够满足绝大多数开发需要。
这一节讲解稍微复杂一点的属性格式。一般在一个控件中只能设置单个的属性为内部默认属性,比如4.4.2.3节中实现的属性:
<cc1:CollectionControlProperty ID="CollectionControlProperty1" runat="server">
<cc1:ListItem ID="ListItem1" runat="server" Text="红色" Value="red">
</cc1:ListItem>
<cc1:ListItem ID="ListItem2" runat="server" Text="蓝色" Value="blue">
</cc1:ListItem>
<cc1:ListItem ID="ListItem3" runat="server" Text="绿色" Value="green">
</cc1:ListItem>
</cc1:CollectionControlProperty>
其中Items属性设置成了内部默认属性,如果控件中需要多个内部默认属性的格式,默认分析器对此是不支持的。如果强行设置了两个默认属性格式的属性,控件可以编译通过,但在页面的属性窗口设置多个复杂属性后,进行代码与设计器视图切换时系统会报以下错误:
这说明不能利用默认的解析器分析多个设置了默认属性格式的子标记。为了解决这个问题,其中一种方法可以重写AddParsedSubObject来定制自己的页面解析子控件方法。主控件核心源代码如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- /// <summary>
- /// 本控件包含三个集合复杂属性: 两个内部默认嵌套形式; 一个内部嵌套形式.
- /// </summary>
- [ToolboxData("<{0}:MultiCollectionControlProperty runat=server></{0}:Multi CollectionControlProperty>")]
- [ParseChildren(false)]
- public class MultiCollectionControlProperty : WebControl
- {
- private Items items;
- [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [NotifyParentProperty(true)]
- [TypeConverter(typeof(CollectionConverter))]
- [Category("复杂属性")]
- [Description("复杂属性——内部默认嵌套形式")]
- public Items Items
- {
- get
- {
- if (this.items == null)
- {
- this.items = new Items();
- }
- return this.items;
- }
- }
- private Items2 items2;
- [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [NotifyParentProperty(true)]
- [TypeConverter(typeof(CollectionConverter))]
- [Category("复杂属性")]
- [Description("复杂属性——内部默认嵌套形式")]
- public Items2 Items2
- {
- get
- {
- if (this.items2 == null)
- {
- this.items2 = new Items2();
- }
- return this.items2;
- }
- }
- private Items3 items3;
- [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [NotifyParentProperty(true)]
- [TypeConverter(typeof(CollectionConverter))]
- [Category("复杂属性")]
- [Description("复杂属性——内部编码嵌套形式")]
- public Items3 Items3
- {
- get
- {
- if (this.items3 == null)
- {
- this.items3 = new Items3();
- }
- return this.items3;
- }
- }
- protected override void AddParsedSubObject(object obj)
- {
- if (obj is ListItem)
- {
- if (this.items == null)
- {
- this.items = new Items();
- }
- this.items.Add((ListItem)obj);
- }
- if (obj is ListItem2)
- {
- if (this.items2 == null)
- {
- this.items2 = new Items2();
- }
- this.items2.Add((ListItem2)obj);
- }
- if (obj is ListItem3)
- {
- if (this.items3 == null)
- {
- this.items3 = new Items3();
- }
- this.items3.Add((ListItem3)obj);
- }
- }
- }
本主控件类包含三个集合复杂属性
:两个内部默认嵌套形式属性(Items和Items2);一个内部编码嵌套形式属性(Items3)。这三个集合类的子项也是使用前面示例中的子项类,其中ListItem3代码省略,它与前面的ListItem1类的内部代码完全一样,只是类名不同。主类MultiCollectionControlProperty前面要加个很重要的元特性 [ParseChildren(false)],指定页解析器把嵌套的内容作为子控件解析。
另外,还可以重写AddParseSubObject,定制自定义的页面解析实现,页面在设计模式下解析(从代码视图切换到设计视图)时,每检测到一个内部子控件都会触发此方法,此方法的参数object就是当前内部子控件标记生成的对象。方法体中的三个if语句分别判断当前对象是什么类型,如果是ListItem类型就把它添加到相关的类集合中,如上面代码把ListItem类型的对象增加到了Items集合中。只有这样,我们在设计视图中查看属性窗口中值时,当前集合才有值显示在属性集合编辑器中(弹出窗口编辑器)。在增加一个子项到集合中时,还要注意第一次往集合中增加子项时,集合值为null,要先为当前集合生成对象实例。
事实上控件中的嵌套标记,不仅可以内置集合类型子标记,还可以增加任意类型的标记,只要子标记具有前缀标志和runat属性即可;如果没有前缀和runat属性,系统也不会报错,只是页面解析器会把不具有前缀和runat属性的整个块标记都用LiteralControl包装后,返回LiteralControl的对象(返回给AddParseSubObject的参数obj),而不管此块有多大。
编译此控件后,拖一个控件到页面中,会在属性窗口中看到三个并列的集合属性,如图4-8所示。
图4-8 属性窗口
为三个集合属性分别设置几个子项,切换到源代码视图会看到如下源代码:
<cc1:MultiCollectionControlProperty ID="MultiCollectionControlProperty1" runat="server">
<cc2:ListItem runat="server" Text="红色" Value="red" ID="ListItem1"> </cc2:ListItem>
<cc2:ListItem runat="server" Text="绿色" Value="green" ID="ListItem2"> </cc2:ListItem>
<cc2:ListItem2 runat="server" Value="blue" ID="ListItem21">蓝色</cc2:ListItem2>
<cc2:ListItem2 runat="server" Value="gray" ID="ListItem22">灰色</cc2:ListItem2>
<cc2:ListItem3 runat="server" Text="黄色" Value="yellow" ID="ListItem31"> </cc2:ListItem3>
<cc2:ListItem3 runat="server" Text="淡蓝" Value="lightblue" ID="ListItem32"> </cc2:ListItem3>
</cc1:MultiCollectionControlProperty>
可以看到,ListItem,ListItem2,ListItem3非常有序地嵌套在主控件内部,从而实现了主控件内部多个复杂默认属性嵌套功能。
AddParseSubObject方法固然能够帮助我们实现控件内部多个复杂默认属性的嵌套功能,但它也有局限性:就是前面提到过的子标记必须是子控件形式标记,且子标记要具有前缀标志和runat属性,否则整个非子控件类型块标记都用LiteralControl包装后,返回LiteralControl的对象(返回给AddParseSubObject的参数obj),而不管此块有多大。
以上是通过重写AddParseSubObject方法实现页面解析功能;另外,.NET Framework为控件设计模式支持专门提供了一个控件构造类:System.Web.UI.ControlBuilder,通过继承此类也可以实现定制页面解析,而且更灵活,后面会专门对比进行介绍。
4.4.3.2 使用ControlBuilder解析复杂内容
通过System.Web.UI.ControlBuilder类定制页面解析逻辑,可以定制任意类型的标记,而不像重写AddParseSubObject方法那样限定子标记必须是子控件,且必须有前缀和runat属性,下面直接通过一个例子来说明一下此类的用法。
首先建立两个文件ScriptItem.cs和ScriptItemCollection.cs,分别定义ScriptItem类和ScriptItemCollection类。其中,ScriptItem类主要存储用户自定义的客户端脚本命令(JavaScript块),ScriptItemCollection可以定义一个集合容器,每个项都是一个 ScriptItem项。与前面讲的集合实现非常类似。这两个类的完整代码如下:
1.ScriptItem类
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- private string _Text;
- [DefaultValue("")]
- [Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design", typeof(UITypeEditor))]
- [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
- [NotifyParentProperty(true)]
- /// <summary>
- /// JavaScript脚本块
- /// </summary>
- public string Text
- {
- get
- {
- return _Text;
- }
- set
- {
- _Text = value;
- }
- }
该类中的
Text就是用于存储用户定义的脚本块;Editor元数据特性指定在属性窗口中Text属性的编辑器是一个下拉块输入编辑器,关于属性编辑器下一节会详细讲解,这里仅知道它的功能即可。需要注意的是,在上一节使用AddParsedSubObject实现页面解析子控件时,要嵌套的三个集合的子标记:ListItem,ListItem2,ListItem3都继承了Control基类,目的是把这些子标记作为子控件(也就具有了前缀和runat属性),而这里的ScriptItem没有继承任何基类,这样就避免了继承一些基类中的冗余属性和方法。
2.ScriptItemCollecton类
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
[ToolboxItem(false)]
public class ScriptItemCollection : List<ScriptItem>
{
public ScriptItemCollection() : base() { }
public ScriptItemCollection(int capacity) : base(capacity) { }
public ScriptItemCollection(IEnumerable<ScriptItem> collection):base (collection) { }
}
u 定义这两个类之后,实现我们自己的ControlBuilder类,可以直接继承该类并实现自己的方法,已经预先实现好了的构造器类代码如下所示:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- public class ScriptItemBuilder : ControlBuilder
- {
- public override Type GetChildControlType(string tagName, IDictionary attributes)
- {
- if (string.Compare(tagName.ToLower(), "scriptitem", false, CultureInfo. InvariantCulture) == 0)
- {
- return typeof(ScriptItem);
- }
- return null;
- }
- public override bool AllowWhitespaceLiterals()
- {
- return false;
- }
- }
在该类中要做的最重要的事情是重写方法
GetChildControlType,在页面解析器分析主控件的每个子标记时,都会调用一次此方法。该方法的第一个参数表示当前正在解析的控件标记字符串,第二个参数表示标记上所有特性的字典集合。方法体中的if语句的功能是,假如当前解析的标记是“scriptitem”(就是后面定义到主控件的集合属性名称),则返回ScriptItem类的类型,且通过ToLower()方法实现不区分大小写。需要注意的是,这里我们做的工作非常简单,只是匹配相应的字符串标记并返回一个类型。而AddParsedSubObject则要自己处理当前对象的值。还有个重写方法AllowWhitespaceLiterals用于指定控件的开始标记和结束标记之间是否允许存在空白。
定义完自己的构造器后,通过为主控件增加如下元数据特性,指定主控件的解析器:
[ControlBuilder(typeof(ScriptItemBuilder))]
u 设置完后,完整的主控件类源代码如下:
- /// <summary>
- /// 获得本书更多内容,请看:
- /// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
- /// </summary>
- [ToolboxData("<{0}:ControlBuilderControl runat=server></{0}:ControlBuilder Control>")]
- [ParseChildren(true, "ScriptItems")]
- [ControlBuilder(typeof(ScriptItemBuilder))]
- public class ControlBuilderControl : WebControl
- {
- private ScriptItemCollection _ScriptItems = new ScriptItemCollection();
- /// <summary>
- /// 脚本命令集合属性
- /// </summary>
- [PersistenceMode(PersistenceMode.InnerProperty)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [Description("工具按钮集设置")]
- [Category("工具按钮——属性设置")]
- public ScriptItemCollection ScriptItems
- {
- get
- {
- if (_ScriptItems == null)
- {
- _ScriptItems = new ScriptItemCollection();
- }
- return _ScriptItems;
- }
- }
- //… …
- }
整个主控件只包括一个集合属性。需要注意的是我们把这个属性定义成内部嵌套标记形式,即我们在
<ScriptItem>标记外面又嵌套了一个<ScriptItems>,把ScriptItems作为主控件的直接嵌套标记。编译此控件后往页面中添加一个控件,并在属性窗口中增加命令项,然后切换到代码视图会看到如下格式的标记代码:
<cc1:controlbuildercontrol >
<ScriptItems>
<cc1:ScriptItem>alert('Hello King');</cc1:ScriptItem>
<cc1:ScriptItem>alert('Hello Rose');</cc1:ScriptItem>
<cc1:ScriptItem>alert('Hello James');</cc1:ScriptItem>
</ScriptItems>
</cc1:controlbuildercontrol>
上面生成了一个具有ScriptItem集合属性的标记集合。与上节我们使用的AddParsedSub Object相比,嵌套标记中多了一个<ScriptItems>内部嵌套标记,且ScriptItem没有前缀和runat属性,如果使用AddParsedSubObject,会把整个<ScriptItems>块(包括其中的ScriptItem)一块作为文本块LiteralControl返回,这显然不符合我们的要求;另外,这里的<ScriptItem>虽然具有前缀,但它也不具有runat的属性,但也能够正确被页面解析器进行正反向解析。
到目前为止,已经分别使用重载基类AddParsedSubObject方法和继承ControlBuilder的构造实现类实现了自定义的页面解析功能。那么使用它们两个的场景是怎样呢?其实在讲解它们的过程中笔者已经作了不少比较,再总结一下:
(1)在绝大多数情况下,如果页面中只要设置一个内部嵌套标记属性或不需要设置内部嵌套标记属性,则不需要重写AddParsedSubObject和实现ControlBuilder的继承类。这两种方式主要是在实现页面中有多个默认嵌套属性时使用。
(2)AddParsedSubObject实现比较简单,仅实现一个方法,一般用于复杂属性单一且比较少的情况。
(3)实现ControlBuilder的定制构造器类比重载AddParsedSubObject要麻烦些,但功能更强,能处理更灵活的嵌套标记。AddParsedSubObject最大的限制是它的内部必须是子控件类型。
(4)两种方式都是ASP.NET提供的两种解决方案,都有它们使用的场景,可以根据自己喜好选择,当习惯使用构造器后,会发现构造器功能更强大、更灵活,用起来更顺手,它可以完全替代重载AddParsedSubObject方式。
到现在为止基本上已经把我们见过的所有的属性标记格式都实现了一遍,4.4.3.2节也把平时很少用到的定制页面解析器功能详细地讲解了一下,其中有些标记在平常开发中比较少用到。本章可以作为查找手册使用,什么时候用到这些内容,什么时候过来查即可。下一节会有更精彩的内容。
4.5 深入研究——定制自己的属性编辑器
对于控件的所有属性,如果都提供非常友好的属性编辑器,使用者使用起来会更加方便。本节主旨就是讲解一下控件属性编辑器,4.5.1节提供一些系统通用编辑器;在复杂属性中,集合属性是最重要的最常用的属性,4.5.2节将主要讲解怎样定制复杂集合类型编辑器以及一些特殊比较酷的编辑器类型。
这些设计器的执行主要是在设计模式下,直接与IDE交互,在编程时可以直接使用System.Windows命名空间开头的一些命名空间下的类。这里首先加入几个本节需要使用到的引用。右击控件库工程,选择“添加引用”命令,如图4-9所示。
选择“添加引用”命令后会打开“添加引用”对话框,如图4-10所示。
图4-9 添加引用
图4-10 “添加引用”对话框
在对话框中找到以下三个引用程序集:
(1)System.Designer
(2)System.Drawing.Design
(3)System.Windows.Forms
单击“确定”按钮,这样在需要使用的地方打开程序集中的命名空间,就可以使用程序集中的系统类了。
4.5.1 系统属性编辑器
很有必要用一小节讲解一下系统提供的一些编辑器。读者对这些编辑器可能都比较熟悉,但它们是怎么使用的呢?其实使用都很简单,仅在每个需要配置的属性前面指定一个标志某种属性编辑器的元数据特性即可。下面就分别介绍一下它们。
4.5.1.1 多行下拉文本属性编辑器
1.配置方式
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
[Editor("System.ComponentModel.Design.MultilineStringEditor,System.Design", typeof(UITypeEditor))]
public string TxtEditor
{
//... ...
}
Editor特性就是指定属性编辑器类型,后面的几种系统属性编辑器类型也是如此。
2.属性浏览器中的效果(如图4-11所示)
图4-11 多行下拉文本属性编辑器
4.5.1.2 色值选择属性编辑器
1.配置方式
[Editor("System.ComponentModel.Design.ColorEditor,System.Design", typeof(UITypeEditor))]
public Color ColorEditor
{
//... ...
}
2.属性浏览器中的效果(如图4-12所示)
图4-12 色值选择属性编辑器
4.5.1.3 文件选择属性编辑器
1.配置方式
[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
public string FileName
{
//... ...
}
2.属性浏览器中的效果(如图4-13所示)
图4-13 文件选择属性编辑器
上图即为单击属性窗口中“文件名属性”按钮弹出的“文件选择属性编辑器”对话框,其实也是调用的Windows系统的“打开文件”对话框。
4.5.1.4 目录选择属性编辑器
1.配置方式
[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
public string FolderNameEditor
{
//... ...
}