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

自定义国家/地区-州控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (8投票s)

2011年1月26日

CPOL

5分钟阅读

viewsIcon

24376

downloadIcon

400

自定义控件,用于显示国家和相应州(省)的列表。

引言

在开发 Web 应用程序表单时,创建国家/州控件组合的需求非常普遍。通常,它从一个国家下拉列表开始。选择特定国家后,它会显示美国和/或加拿大的州下拉列表,或者为其他国家显示一个自由文本输入框。一些应用程序不需要那些没有州的国家的州信息,因此,应该有一个标志来显示(或不显示)文本框。

这种看似简单的情况需要在您想放置这两个控件的每个页面上进行一些编码。当然,这可以通过多种方式实现:使用 JavaScript 在客户端,使用 AJAX,jQuery 等...

对于我的应用程序,我想要一些真正轻量级且方便的东西,可以消除大量编码,易于实现,并允许 ASP.NET 原生验证。

从一开始,我就决定在更改国家时重新加载页面,而不是通过 JavaScript 处理州。这种方法有利有弊。一个主要的缺点是您必须重新加载页面,这会增加一次到服务器的往返。对于一个非常繁忙的页面,这可能是一个问题,因为您必须保留已插入页面控件的所有数据。但这就是 ViewState 的作用,不是吗?优点是,这种方法不需要任何 JavaScript 代码。

所以,我列出了一些要求:

  1. 应该有两个控件:一个用于国家,一个用于州。
  2. 州控件应该能够分配相应的国家控件。
  3. 每个控件必须能够独立工作。
  4. 每个控件必须具有默认设置(国家和州)。
  5. 每个控件必须能够通过原生验证控件进行验证。
  6. 当国家没有预定义的州列表时,州控件应显示为下拉列表或文本框。
  7. 州控件必须有一个标志,指示在国家没有州的情况下是否显示文本框。

考虑到这一点,我决定使用以下 XML 文档作为国家/州的数据源,我称之为“TheWorld.xml”。

<world>
  <countries>
    <country>
      <name>AFGHANISTAN</name>
      <code>AFG</code>
      <states />
    <country>
      <name>ALBANIA</name>
      <code>ALB</code>
      <states />
    </country>
   ..............................................

    <country>
      <name>CAMEROON</name>
      <code>CAM</code>
      <states />
    </country>
    <country>
      <name>CANADA</name>
      <code>CAN</code>
      <states>
        <state>
          <name>ALBERTA</name>
          <code>AB</code>
        </state>
        <state>
          <name>BRIT. COLUMBIA</name>
          <code>BC</code>
        </state>
        <state>
          <name>MANITOBA</name>
          <code>MB</code>
        </state>
        <state>
          <name>N.W. TERRITORY</name>
          <code>NT</code>
        </state>
        <state>
          <name>NEW BRUNSWICK</name>
          <code>NB</code>
        </state>
        <state>
          <name>NEWFOUNDLAND</name>
          <code>NF</code>
        </state>
        <state>
          <name>NOVA SCOTIA</name>
          <code>NS</code>
        </state>
        <state>
          <name>ONTARIO</name>
          <code>ON</code>
        </state>
        <state>
          <name>PR. EDWARD IS.</name>
          <code>PE</code>
        </state>
        <state>
          <name>QUEBEC</name>
          <code>PQ</code>
        </state>
        <state>
          <name>SASKATCHEWAN</name>
          <code>SK</code>
        </state>
        <state>
          <name>YUKON TERR.</name>
          <code>YK</code>
        </state>
      </states>
    </country>
    <country>
      <name>CAPE VERDE</name>
      <code>CAP</code>
      <states />
    </country>
..............................................
</countries>
</world>

正如您所见,结构非常简单,可以轻松维护列表的最新状态。在我的应用程序中,我将此文件编译为 Web 资源。通常,我将所有自定义控件放在一个类库中,我可以将其包含到任何 Web 项目中。使用任何文档(图像、CSS 文件、JavaScript 文件、XML 文件等)作为 Web 资源,允许您将其放入编译格式,而无需担心将其从一个应用程序移动到另一个应用程序。使用此 链接 阅读有关 Web 资源的更多信息。

正如我之前写过的,我想通过州控件的 ID 来指定国家控件与州控件的关联。这意味着在我的应用程序中,我应该能够通过其 ID 找到该控件。

您知道 FindControl() 函数只会在特定级别的控件列表中查找。我更希望能够通过 ID 在所有级别中查找控件。这就是为什么我在网上找到了一个非常有用的 博客

我重用了 Steve Smith 编写的代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;

namespace ControlLibrary
{
    public static class ControlFinder
    {
        /// <summary>
        /// Similar to Control.FindControl, but recurses through child controls.
        /// </summary>
        public static T FindControl<T>(Control startingControl, 
                      string id) where T : Control
        {
            T found = startingControl.FindControl(id) as T;
            if (found == null)
            {
                found = FindChildControl<T>(startingControl, id);
            }

            return found;
        }


        /// <summary>     
        /// Similar to Control.FindControl, but recurses through child controls.
        /// Assumes that startingControl is NOT the control you are searching for.
        /// </summary>
        public static T FindChildControl<T>(Control startingControl, 
                        string id) where T : Control
        {
            T found = null;
            foreach (Control activeControl in startingControl.Controls)
            {
                found = activeControl as T;
                if (found == null || (string.Compare(id, found.ID, true) != 0))
                {
                    found = FindChildControl<T>(activeControl, id);
                }

                if (found != null)
                {
                    break;
                }
            }
            return found;
        }
    }
}

一切都为控件准备就绪,现在我们可以开始了。

国家控件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;

 namespace ControlLibrary
{
   

public class CountryListControl : System.Web.UI.WebControls.DropDownList
    {
        public string DefaultCountry { get; set; }

        protected override void OnLoad(EventArgs e)
        {
            if (!this.Page.IsPostBack)
            {
                string URLString = this.Page.ClientScript.GetWebResourceUrl(
                       typeof(CountryListControl), 
                       "LOLv2.Resources.TheWorld.xml");
                URLString = this.Page.Request.Url.Scheme + "://" + 
                            this.Page.Request.Url.Authority + URLString;
                XmlDocument doc = new XmlDocument();
                doc.Load(URLString);

                this.DataTextField = "InnerText";
                this.DataSource = doc.SelectNodes("world/countries/country/name");
                this.DataBind();

                if (!string.IsNullOrEmpty(DefaultCountry))
                    this.SelectedValue = DefaultCountry;
                base.OnPreRender(e);
            }
        }
    }
}

该控件继承自 DropDownList。该控件定义了 DefaultCountry 属性。它还有一个 OnLoad 事件:当页面第一次加载时,XML 文档将从 Web 资源创建。DataSource 被设置为包含国家名称的 XmlNodeList 并绑定到列表。如果分配了 DefaultCountry,则将其设置为选定值。

州控件

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;
using System.Xml;
using System.Web.UI.WebControls;
using System.Web.UI;
using LOLv2;

namespace ControlLibrary
{
    public class StateListControl : DropDownList 
    {
        public string CountryControl { get; set; }
        public string Country { get; set; }
        public bool DisplayEmptyTextBox { get; set; }
        public string DefaultState;



        private TextBox textBox;
        private DropDownList countryDll;
        private bool hasStates;

        protected override void OnLoad(EventArgs e)
        {
            this.Visible = true;
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (!string.IsNullOrEmpty(CountryControl))
            {
                countryDll = ControlFinder.FindControl<DropDownList>(
                                this.Page, CountryControl);
            }


            if (null != countryDll)
                Country = countryDll.SelectedItem.Value;
            else
                Country = string.IsNullOrEmpty(Country) ? "UNITED STATES" : Country;

            string URLString = this.Page.ClientScript.GetWebResourceUrl(
                   typeof(StateListControl), "LOLv2.Resources.TheWorld.xml");
            URLString = this.Page.Request.Url.Scheme + "://" + 
                        this.Page.Request.Url.Authority + URLString;
            XmlDocument doc = new XmlDocument();
            doc.Load(URLString);


            XmlNodeList stateNames = doc.SelectNodes("world/countries/country[name=\"" + 
                                     Country + "\"]/states/state/name");
            XmlNodeList stateCodes = doc.SelectNodes("world/countries/country[name=\"" + 
                                     Country + "\"]/states/state/code");


            if (stateNames.Count == 0)
            {
                textBox = new TextBox();
                hasStates = false;
            }
            else
            {
                hasStates = true;

                this.Items.Clear();
                for (int i = 0; i < stateNames.Count; i++)
                {
                    ListItem li = new ListItem();
                    li.Text = stateNames[i].InnerText;
                    li.Value = stateCodes[i].InnerText;
                    this.Items.Add(li);
                }




                if (!string.IsNullOrEmpty(DefaultState))
                    this.SelectedValue = DefaultState;
                else
                    if (null != this.Page.Request[this.ClientID])
                        try
                        {
                            this.SelectedValue = this.Page.Request[this.ClientID];
                        }
                        catch { }
            }
            base.OnPreRender(e);

        }

        protected override void Render(HtmlTextWriter writer)
        {

            if (null != textBox)
            {
                CopyProperties(this, textBox);
                textBox.AutoPostBack = this.AutoPostBack;
                textBox.CausesValidation = this.CausesValidation;
                textBox.RenderControl(writer);
            }
            else
                if (hasStates)
                    base.Render(writer);
        }

        private void CopyProperties(WebControl from, WebControl to)
        {
            to.ID = from.ID;
            to.CssClass = from.CssClass;

            ICollection keys = from.Attributes.Keys;
            foreach (string key in keys)
            {
                to.Attributes.Add(key, from.Attributes[key]);
            }

            to.BackColor = from.BackColor;
            to.BorderColor = from.BorderColor;
            to.BorderStyle = from.BorderStyle;
            to.BorderWidth = from.BorderWidth;

            to.ForeColor = from.ForeColor;
            to.Height = from.Height;
            ICollection styles = from.Style.Keys;
            foreach (string key in keys)
            {
                to.Style.Add(key, from.Style[key]);
            }

            to.ToolTip = from.ToolTip;
            to.Visible = from.Visible;
            to.Width = from.Width;
        }
    }
}

正如您所见,州控件更复杂。

该控件继承自 DropDownList。它具有以下属性:

  • 公共字符串 CountryControl,它是一个分配的国家控件 ID。
  • 公共字符串 Country,这是国家名称。
  • 公共布尔值 DisplayEmptyText,它指示当国家没有州时显示或不显示文本框。
  • 公共字符串 DefaultState,它定义了要设置为默认值的州。
  • 私有 TextBox 对象 textbox,当国家没有州时,它将用于渲染文本框而不是下拉列表。
  • 私有 DropDownList contryDll,它用作找到的国家控件的占位符。
  • 私有布尔值 hasStates,它只是一个标志。

pageLoad 事件中,我将控件的可见性设置为 true。这样做是为了在回发时不会丢失控件,如果控件在回发之前未显示。

Prerender 事件中:

  1. 查找国家控件。
  2. 为此控件定义国家。
  3. 从 Web 资源获取数据。
  4. 填充现有的下拉列表,或创建一个新的文本框对象。

使用渲染过程。

如果存在州列表,则渲染下拉列表,否则渲染文本框。您可能会注意到,我使用 CopyProperties 过程将原始控件的任何可能属性复制到文本框中(如果渲染的是文本框)。

结果

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeBehind="Default.aspx.cs" Inherits="CountryStateList.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ Register Assembly="ControlLibrary" 
         Namespace="ControlLibrary" TagPrefix="cl1" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <cl1:CountryListControl ID="ddlCountry" 
            runat="server" DefaultCountry="CANADA" 
            AutoPostBack="true">
        </cl1:CountryListControl>
        <br />
        <cl1:StateListControl ID="ddlState" runat="server" 
            CountryControl="ddlCountry" CssClass="ffff"
            AutoPostBack="true" DisplayEmptyTextBox="true">
        </cl1:StateListControl>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
            runat="server" ControlToValidate="ddlState"
            ErrorMessage="*"></asp:RequiredFieldValidator>
        <br />
        <asp:Button ID="btnClick" runat="server" Text="Click" />
    </div>
    </form>
</body>
</html>

这是 Default.aspx 页面的示例。您需要注册程序集。

<%@ Register Assembly="ControlLibrary" 
             Namespace="ControlLibrary" TagPrefix="cl1" %>

我放置了 RequiredFieldValidator 来展示它与控件一起工作。

您现在可以看到处理 Web 页面上的国家/州功能的繁琐任务是多么方便和简单。您可以从本文顶部的链接下载源代码。如果您喜欢这篇文章,请投票。

© . All rights reserved.