65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 颜色下拉控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (38投票s)

2004 年 7 月 12 日

CPOL

8分钟阅读

viewsIcon

371546

downloadIcon

5602

在 ASP.NET 服务器控件中持久化和解析自定义集合的示例。

引言

在为像 Visual Studio 或 WebMatrix 这样的设计器环境开发控件时,将控件属性持久化为序列化的 HTML 的复杂性可能会出现。在许多情况下,控件属性作为属性或嵌套子标签的内置序列化就足够了。控件开发人员也可以使用几个代码属性来对这个过程进行一定程度的自定义。然而,在某些情况下,开发人员需要对控件属性如何作为 HTML 持久化有更大程度的控制。

在本文中,我将介绍一个自定义的 ASP.NET 服务器控件,用于在下拉列表中显示颜色选择。在描述完控件之后,我将重点讨论在 Visual Studio Web Form 设计器上下文中持久化自定义项目集合的问题,并提供一个使用自定义 ControlDesignerControlBuilder 对象的解决方案。

HtmlColorDropDown 控件

HtmlColorDropDown Web 控件封装了一个 HTML 下拉列表(即 <select> 标签),其中包含作为项目的颜色(<option> 标签)。该控件可以将项目本身作为前景色或背景色呈现在 <option> 标签中。所提供的颜色选择由 Palette 属性的值决定,该属性可以是以下之一:

所有命名颜色

System.Drawing.KnownColor 枚举中所有已命名的颜色(不包括系统颜色)。

简单

一小部分常见的命名颜色。

Web 安全色

216 种被认为是“网络安全”的颜色;它们只使用 #FF、#CC、#99、#66、#33 和 #00 作为红色、绿色和蓝色分量的十六进制值。

用户自定义

由控件用户通过 UserDefinedColors 属性标识的颜色。

UserDefinedColors 属性是一个 ColorCollection 对象。ColorCollection 是一个强类型集合类,继承自 CollectionBase,使用标准 System.Drawing.Color 对象作为项目。

DisplayColorMode 属性控制颜色将如何在每个项目中显示(如果有的话)

背景色

使用其颜色作为背景显示每个项目;前景文本以黑色或白色显示,具体取决于哪种颜色对比度更好。

前景色

使用其颜色作为前景文本显示每个项目。

纯色

不在项目中显示颜色。

选定的项目通过 SelectedColor 属性作为 Color 对象可用。属性 SelectedColorNameSelectedColorHexString 分别返回所选颜色的名称(对于命名颜色)和 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 时,就会出现问题。通常,我们会将集合属性上的 PersistenceModeDesignerSerializationVisibility 属性设置为将集合项持久化为嵌套标签,如下所示:

    [
        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) 属性应用于控件,以确保属性不会以嵌套服务器控件标签的形式持久化。PersistenceModeDesignerSerializationVisibility 属性仍然应用于 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'> 子标签返回类型 ColorItemHelperColorItemHelper 类定义非常简单:

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 集合。尽管自定义属性持久化和解析的技术可能很复杂,但这些技术对控件作者来说具有重要的好处,值得考虑。

© . All rights reserved.