ASP.NET 颜色下拉控件
在 ASP.NET 服务器控件中持久化和解析自定义集合的示例。
引言
在为像 Visual Studio 或 WebMatrix 这样的设计器环境开发控件时,将控件属性持久化为序列化的 HTML 的复杂性可能会出现。在许多情况下,控件属性作为属性或嵌套子标签的内置序列化就足够了。控件开发人员也可以使用几个代码属性来对这个过程进行一定程度的自定义。然而,在某些情况下,开发人员需要对控件属性如何作为 HTML 持久化有更大程度的控制。
在本文中,我将介绍一个自定义的 ASP.NET 服务器控件,用于在下拉列表中显示颜色选择。在描述完控件之后,我将重点讨论在 Visual Studio Web Form 设计器上下文中持久化自定义项目集合的问题,并提供一个使用自定义 ControlDesigner
和 ControlBuilder
对象的解决方案。
HtmlColorDropDown 控件
HtmlColorDropDown
Web 控件封装了一个 HTML 下拉列表(即 <select>
标签),其中包含作为项目的颜色(<option>
标签)。该控件可以将项目本身作为前景色或背景色呈现在 <option>
标签中。所提供的颜色选择由 Palette
属性的值决定,该属性可以是以下之一:
|
|
|
一小部分常见的命名颜色。 |
|
216 种被认为是“网络安全”的颜色;它们只使用 #FF、#CC、#99、#66、#33 和 #00 作为红色、绿色和蓝色分量的十六进制值。 |
|
由控件用户通过 |
UserDefinedColors
属性是一个 ColorCollection
对象。ColorCollection
是一个强类型集合类,继承自 CollectionBase
,使用标准 System.Drawing.Color
对象作为项目。
DisplayColorMode
属性控制颜色将如何在每个项目中显示(如果有的话)
|
使用其颜色作为背景显示每个项目;前景文本以黑色或白色显示,具体取决于哪种颜色对比度更好。 |
|
使用其颜色作为前景文本显示每个项目。 |
|
不在项目中显示颜色。 |
选定的项目通过 SelectedColor
属性作为 Color
对象可用。属性 SelectedColorName
和 SelectedColorHexString
分别返回所选颜色的名称(对于命名颜色)和 HTML 十六进制值(例如,“#FFC309”)作为字符串。这些都是读/写属性;其中一个的设置会影响所有三个的值。如果尝试将 SelectedColor
设置为当前 Palette
中不存在的颜色,则会分配 Color.Empty
值。同样,如果 Palette
更改,如果其现有值不在新的选择列表中,SelectedColor
可能会设置为 Color.Empty
。
请注意,在 <option>
标签中渲染的前景色或背景色的正确显示取决于浏览器。大多数现代浏览器通过 style
属性将标识为 CSS 属性的项目颜色解释得很好。然而,有趣的是,并非所有浏览器在下拉列表折叠时都会渲染所选项目的颜色。为了解决这个问题,提供了 AutoColorChangeSupport
属性。当其值为 true
(其默认值)时,会随控件渲染一个小型的 JavaScript onChange
处理程序,导致所选项目的颜色被分配给父级 <select>
标签。可以将该属性设置为 false
以防止此行为。
控件的默认事件是 ColorChanged
。此事件在回发到服务器之间下拉列表中的选择发生更改时触发。以下是一个处理 ColorChanged
事件的 .aspx 页面的示例:
<%@ Page language="c#" %>
<%@ Register TagPrefix="cc1" Namespace="UNLV.IAP.WebControls"
Assembly="UNLV.IAP.WebControls.HtmlColorDropDown" %>
<script runat="server">
private void HtmlColorDropDown1_ColorChanged(object sender,
System.EventArgs e)
{
Label1.Text = HtmlColorDropDown1.SelectedColorName;
}
</script>
<html>
<head>
<title>Example</title>
</head>
<body>
<form runat="server">
<cc1:HtmlColorDropDown id="HtmlColorDropDown1" runat="server"
DisplaySelectColorItemText="--Select a Color--"
DisplaySelectColorItem="True"
AutoPostBack="True"
OnColorChanged="HtmlColorDropDown1_ColorChanged">
</cc1:HtmlColorDropDown>
<P>The selected color is: <asp:Label id="Label1" runat="server"/></P>
</form>
</body>
</html>
有关所有 HtmlColorDropDown
属性和事件的完整描述,请参阅可下载的控件文档。
问题:持久化 UserDefinedColors 集合
这个控件的设计考虑了可视化设计器(例如,Visual Studio)和文本编辑器(例如,记事本)环境。对于设计器环境,我们继承了许多功能,无需任何额外代码。例如,因为我们对 SelectedColor
属性使用常规的 System.Drawing.Color
对象,所以标准 ColorBuilder
在 Visual Studio Web 窗体设计器中可用。
同样,由于我们在 UserDefinedColors
集合中有标准的 Color
对象,Visual Studio 集合构建器是免费提供的。
然而,当需要将此颜色集合序列化为服务器标签 HTML 时,就会出现问题。通常,我们会将集合属性上的 PersistenceMode
和 DesignerSerializationVisibility
属性设置为将集合项持久化为嵌套标签,如下所示:
[
PersistenceMode(PersistenceMode.InnerDefaultProperty)
,DesignerSerializationVisibility(
DesignerSerializationVisibility.Content)
]
public ColorCollection UserDefinedColors
{
get {...}
set {...}
}
设置了这些属性后,我们可以持久化 UserDefinedColors
集合。但是,标记显示为冗长的 <System.Drawing.Color>
标签,如下所示:
<cc1:htmlcolordropdown id="HtmlColorDropDown1" runat="server"
Palette="UserDefined">
<System.Drawing.Color IsEmpty="False" A="255" B="0" IsNamedColor="True"
IsKnownColor="True" Name="Red" G="0" R="255" IsSystemColor="False">
</System.Drawing.Color>
<System.Drawing.Color IsEmpty="False" A="255" B="0" IsNamedColor="True"
IsKnownColor="True" Name="Green" G="128" R="0" IsSystemColor="False">
</System.Drawing.Color>
<System.Drawing.Color IsEmpty="False" A="255" B="255" IsNamedColor="True"
IsKnownColor="True" Name="Blue" G="0" R="0" IsSystemColor="False">
</System.Drawing.Color>
</cc1:htmlcolordropdown>
问题不仅在于持久化的颜色信息远超我们所需或所想。由于这些属性表示 Color
对象的只读属性,因此在解析时会抛出异常。最好以不同的方式持久化这些内部项目,仅包含最少必要的信息,如下所示:
<cc1:htmlcolordropdown id="HtmlColorDropDown1" runat="server"
Palette="UserDefined">
<Color value="Red"/>
<Color value="Green"/>
<Color value="Blue"/>
</cc1:htmlcolordropdown>
这使得使用纯文本编辑器的用户更容易使用该控件。自定义的 ControlDesigner
可以帮助我们以这种简化方式持久化内部标记,而自定义的 ControlBuilder
将有助于在解析时反序列化 <Color value="xxx"/>
标签。
通过 ControlDesigner 实现自定义持久化
为了持久化 UserDefinedColors
集合,我们将定义一个 System.Web.UI.Design.ControlDesigner
的自定义子类。ControlDesigner
的目的是为 Web 控件提供设计时支持,并且可以通过多种方式实现。例如,ControlDesigner
可以用于自定义在 Visual Studio 等可视化设计器中表示控件的 HTML。ControlDesigner
还可以用于添加对控件模板的可视化编辑的支持。
对于 HtmlColorDropDown
控件,我们需要能够自定义当可视化设计器将对象的属性持久化到网页时序列化的内部 HTML。为此,我们可以子类化 ControlDesigner
并覆盖 GetPersistInnerHtml
方法。子类化 HtmlColorDropDownDesigner
的完整代码如下:
public class HtmlColorDropDownDesigner : ControlDesigner
{
public override string GetPersistInnerHtml()
{
StringWriter sw = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(sw);
HtmlColorDropDown dd
= this.Component as HtmlColorDropDown;
if (dd != null)
{
// for each color in the collection, output its
// html known name (if it is a known color)
// or its html hex string representation
// in the format:
// <Color value='xxx' />
foreach(Color c in dd.UserDefinedColors)
{
string s =
(c.IsKnownColor ? c.Name
: ColorUtility.ColorToHexString(c) );
html.WriteBeginTag("Color");
html.WriteAttribute("value", s);
html.WriteLine(HtmlTextWriter.SelfClosingTagEnd);
}
}
return sw.ToString();
}
}
在重写的 GetPersistInnerHtml
方法中,我们遍历 UserDefinedColors
属性中的每个 System.Drawing.Color
项。对于每种颜色,我们输出一个子标签,格式为:
<Color value="xxx">
其中 xxx 是颜色的名称(对于已知颜色项,例如“Red”)或其 HTML 十六进制字符串(例如,“#FF0000”)。通过这种自定义持久化,我们可以表示完整的 UserDefinedColors
集合,而不会出现序列化完整 Color
对象的问题和冗长。
值得注意的是,还有其他方法可以自定义控件属性的持久化方式。例如,可以为自定义类型定义 TypeConverter
,然后它可以在序列化中发挥作用。同样,自定义的 CodeDomSerializer
对象可以将对象属性持久化为编程代码而不是 HTML。在 HtmlColorDropDown
控件的案例中,我选择了自定义 ControlDesigner
并覆盖 GetPersistInnerHtml
,因为它为持久化 ColorCollection
的内容提供了一个简单直接的解决方案。
自定义设计器通过 Designer
属性与主 HtmlColorDropDown
控件相关联。在此上下文中,其他属性也很重要。PersistChildren(false)
属性应用于控件,以确保属性不会以嵌套服务器控件标签的形式持久化。PersistenceMode
和 DesignerSerializationVisibility
属性仍然应用于 UserDefinedColors
属性,以确保我们的设计器的 GetPersistInnerHtml
方法确实有被调用的理由。
[
//...(other attributes)...//
,PersistChildren(false)
,Designer(typeof(HtmlColorDropDownDesigner))
]
public class HtmlColorDropDown : Control, IPostBackDataHandler
, IPostBackEventHandler, IAttributeAccessor
{
...
[
//...(other attributes)...//
,PersistenceMode(PersistenceMode.InnerDefaultProperty)
,DesignerSerializationVisibility(
DesignerSerializationVisibility.Content)
]
public ColorCollection UserDefinedColors
{
get {...}
set {...}
}
...
}
通过 ControlBuilder 进行自定义解析
自定义的 HtmlColorDropDownDesigner
负责将 UserDefinedColors
集合持久化为服务器标签的内部 HTML 内容。当页面被解析时,三个独立的类协同工作,将这个内部内容反序列化回 UserDefinedColors
集合:一个自定义的 ControlBuilder
,一个表示 <Color value='xxx'/>
标签的辅助类,以及主 HtmlColorDropDown
控件本身。
这三者中的第一个是 System.Web.UI.ControlBuilder
的子类。ControlBuilder
对象在页面解析器从标记文本构建服务器控件时提供帮助。HtmlColorDropDownBuilder
定义如下,只有一个重写方法:
public class HtmlColorDropDownBuilder : ControlBuilder
{
public override Type GetChildControlType(string tagName,
IDictionary attribs)
{
if (string.Compare(tagName,"color", true) == 0)
{
return typeof(ColorItemHelper);
}
return base.GetChildControlType (tagName, attribs);
}
}
自定义构建器的 GetChildControlType
方法为服务器控件标记中嵌套的每个 <Color value='xxx'>
子标签返回类型 ColorItemHelper
。ColorItemHelper
类定义非常简单:
public class ColorItemHelper
{
private string _value;
public string Value
{
get {return _value;}
set {_value = value;}
}
}
自定义生成器通过 ControlBuilder
属性分配给 HtmlColorDropDown
控件。然后,主 HtmlColorDropDown
控件使用解析器传递给它的 ColorItemHelper
对象将颜色添加到其 UserDefinedColors
集合中。这是通过重写 Control
类的 AddParsedSubObject
方法来完成的:
[
//...(other attributes)...//
, ParseChildren(false)
, ControlBuilder(typeof(HtmlColorDropDownBuilder))
]
public class HtmlColorDropDown : Control, IPostBackDataHandler
, IPostBackEventHandler, IAttributeAccessor
{
...
protected override void AddParsedSubObject(object obj)
{
if (obj is ColorItemHelper)
{
ColorItemHelper h = obj as ColorItemHelper;
this.UserDefinedColors.Add(h.Value);
}
else
base.AddParsedSubObject (obj);
}
...
}
请注意,ParseChildren(false)
属性应用于主 HtmlColorDropDown
类。这确保了嵌套的内部 HTML 不会被解析为控件的属性,从而允许我们的自定义解析功能正常运行。
摘要
HtmlColorDropDown
Web 控件渲染一个标准的 HTML <select>
标签,其中包含表示颜色选择的 <option>
项目。颜色本身可以作为每个项目的前景色或背景色渲染。提供了三种内置调色板;第四种,UserDefined
,允许控件用户指定在列表中提供哪些颜色作为选择。为了支持在可视化设计器中持久化用户定义的颜色,我使用了自定义的 ControlDesigner
,重写了 GetPersistInnerHtml
方法,将颜色渲染为简单的 <Color value='xxx'/>
子标签。我还使用了一个自定义的 ControlBuilder
来协助解析嵌套标签,以反序列化 UserDefinedColors
集合。尽管自定义属性持久化和解析的技术可能很复杂,但这些技术对控件作者来说具有重要的好处,值得考虑。