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

深入了解 CascadingDropDown

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.08/5 (6投票s)

2007年12月5日

CPOL

9分钟阅读

viewsIcon

77166

本文档将帮助您了解 CascadingDropDown 的工作原理以及如何使用它。

引言

CascadingDropDownExtender 是 AjaxControlToolkit 中最受欢迎的扩展程序之一。但是,关于该控件的服务器端部分已经有了一个帮助文档,而关于(真正使其工作的)客户端部分的内容却几乎没有,这让开发者在需要进行一些高级的事件处理或自定义时感到困惑。

我认为,如果您了解它的工作原理,那么实现自己的功能会更容易。因此,在本文中,我将描述 CascadingDropDown 的工作原理,然后尝试回答一些常见问题。

简述服务器端

简而言之,对于不了解的人来说,CascadingDropDown 是一个扩展程序控件,用于根据在一个 DropDownList 中选择的值来填充另一个 DropDownList 中的数据。它通过 AJAX 使用 Web 方法获取所有必要的数据。现在,让我们更详细地考虑它的组织。

因此,CascadingDropDownDropDownList 的一个扩展程序。作为目标控件,我们设置“依赖的”(或子)DropDownList(即将被填充的那个)。要设置父控件的 ID,我们使用 ParentControlID 属性,顺便说一句,该属性不是必需的(即可以没有父 DropDownList)。除了(TargetControlID)之外,其他必需的属性是 CategoryServiceMethodServiceMethod 包含 CascadingDropDown 使用的 Web 方法的名称,而 Category 则充当筛选器名称,并作为参数之一传递给 Web 方法。可选属性有:

  • PromptText(在用户从 DropDownList 中选择值之前显示的文本),
  • PromptValue(显示 PromptText 时设置的值),
  • EmptyText(当 DropDownList 没有数据可显示时显示的文本),
  • EmptyValue(显示 EmptyText 时设置的值),
  • LoadingText(加载期间显示的文本),
  • ServicePath(Web 服务的路径;如果不声明,则使用 PageMethod),
  • ContextKey(用于存储任何附加信息的空间),
  • UseContextKey(指示是否将 ContextKey 作为属性之一传递给 Web 方法),
  • SelectedValue(子 DropDownList 的默认值)。

除了上面描述的属性之外,CascadingDropDown 还有几个辅助的静态方法可以在 Web 方法中使用,但稍后将讨论。CascadingDropDown 在服务器端所做的所有事情就是将上述属性的值收集到 脚本描述符中,并将其“发送”到派生自 Sys.UI.Behavior(准确地说,是派生自 AjaxControlToolkit.BehaviorBase)的相应客户端类。然后,所有主要过程都在客户端运行。

强大的客户端

initialize 方法中,会覆盖订阅到子 select 和父(如果定义了) select DOM 元素的 change 事件的处理程序。此外,如果指定了父 select,则父子 select 之间会建立一种联系:子 select 会接收一个指向父 selectCascadingDropDownParentControlID 字段,而父 select 会(如果还没有)获得一个名为 childDropDown 的数组字段,其中添加了指向子 select 的引用。除了所有这些之外,在初始化阶段,会从子 select DOM 元素中移除所有选项,并向其添加 CascadingDropDownCategory 字段(该字段将包含 Category 属性的值),在父 select 中插入 PromptTextLoadingText 属性中的文本,并执行一些其他小的操作。此外,如果子 select 没有父,或者指定了父,并且在调用 initialize 时,其选定值不等于 PromptValueEmptyValue,那么子 select 将会被数据填充(也就是说,会执行与用户更改父 select 值相同的操作)。所以,现在是时候看看数据是如何填充的了。

一切都始于 _onParentChange 方法(注意下划线前缀 - 此方法不供公开使用)。如前所述,Web 方法会根据 UseContextKey 的值传递 2 或 3 个参数。这些参数是 knownCategoryValuescategorycontextKey。所有这些参数都是字符串。逻辑上,_category_contextKey 字段值被作为第二个和第三个参数传递。第一个参数的值更有趣。它的格式如下:如果子 select 没有父,则值为 null。如果指定了父 select,则会将它的 CascadingDropDownCategory 字段值和选定值添加到 knownCategoryValues 中。如果父 select 指定了 CascadingDropDownParentControlID 字段,则会获取父的父 select,并重复上述操作,直到达到层次结构的末尾。新的键值对会添加到 knownCategoryValues 的开头,结果,knownCategoryValues 会变成这样:

'category1:value1;category2:value2;...'

这里,我想提醒大家注意一个不明显的细节。要使这一切正常工作,父 select **必须**指定 CascadingDropDownCategory 字段。如果有一个 CascadingDropDownBehavior 客户端控件,并且该控件以父 select 作为目标控件,则会自动指定此字段。否则,您必须自己定义该字段。

但让我们回到 Web 方法。它不是通过生成的代理调用的,而是直接通过 Sys.Net.WebServiceProxy.invoke 方法调用的。收集完 Web 方法的所有参数后,会引发 populatingObjectSys.CancelEventArgs)客户端事件,如果未将 Sys.CancelEventArgs.cancel 属性在处理程序中设置为 false,则会将请求发送到服务器。Web 方法应返回 CascadingDropDownNameValue 对象的数组。

Screenshot - CascadingDropDownNameValue.jpg

如果 Web 方法调用失败,则错误信息(出于某个我不知道的原因)将作为选项放在子 select 中。如果 Web 方法调用成功,则子 select选项集合将用接收到的值填充,并且如果其中一个接收到的对象具有 CascadingDropDownNameValue.value 字段等于 SelectedValue,或者 CascadingDropDownNameValue.isDefaultValue == true,则会选择其值。无论是否存在错误,最后都会引发 populatedObjectSys.EventArgs)客户端事件。好了,更新完成。

值得一提的是,在子 select 的选定值发生任何更改时,都会引发 SelectionChanged(Object, AjaxControlToolkit.CascadingDropDownSelectionChangedEventArgs) 客户端事件。

Screenshot - CascadingDropDownSelectionChangedEventArgs.jpg

在撰写本文时,存在一个 bug。LoadingTextpopulating 事件引发之前设置,并且如果请求被取消,LoadingText 会保留在子 select 中,而不是 promptText 或旧选项,这可能会让用户感到困惑。

当我们了解更多时...

现在,我们对工作原理有了或多或少的了解,可以得出一些结论。

  • 由于 CascadingDropDown 通过 Web 方法(Web 服务方法或 PageMethod)工作,因此它与服务器端或客户端的页面生命周期无关。因此,当 CascadingDropDown 更新 select DOM 元素时,不会发生回发,不会激活 ASP.NET 验证器,不会引发生命周期事件,SelectedIndexChanged 等等也不会工作。此外,为了使 CascadingDropDown 工作,您无需将其放入 UpdatePanel 中——它反正也不会使用它。
  • 由于 CascadingDropDown 通过 Web 方法工作,因此参数的顺序并不重要。重要的是 Web 方法的签名与参数的类型和**名称**匹配。也就是说,参数的名称**必须**与文档中给出的名称相同。同样,无论您使用的是 Web 服务方法还是 PageMethod,这都是无关紧要的。
  • CascadingDropDown 没有服务器端逻辑(不包括 Web 方法,因为它不是控件的一部分)。控件的服务器端部分所做的只是帮助我们简化客户端部分的创建。这就是为什么 CascadingDropDownBehavior 可以独立于服务器端创建和使用,而且,更重要的是,完全不需要 ASP.NET 2.0。只需将必要的脚本添加到页面并使用 $create 方法即可。
Sys.Application.add_init(function() {
    $create(
        AjaxControlToolkit.CascadingDropDownBehavior, 
        {
            "Category":"Model",
            "ClientStateFieldID":"hiddenFieldClientStateId",
            "ParentControlID":"parentDropDownList",
            "PromptText":"Please select a model",
            "ServiceMethod":"GetDropDownContents",
            "ServicePath":"MyService.asmx",
            "id":"exCascadingDropDown"
        },
        null,
        null,
        $get("childDropDownList")
    );
}); 

我们在这里还没有考虑的是 ClientStateFieldID 属性。我们需要将某个隐藏字段的 ID 传递给此属性,以便 CascadingDropDownBehavior 可以使用此隐藏字段在回发期间存储选定的值(称为“ClientState”)。

  • UpdatePanel 不同,通过 Sys.Net.WebServiceProxy.invoke 发送的请求是独立执行的。这就是为什么如果请求执行时间过长,我们可能会在客户端遇到并发问题。例如:用户在父 select 中选择了一个值。此操作将发送第一个请求。在等待响应之前,用户在同一个父 select 中选择了另一个值。此时,第二个请求被发送。假设执行第二个请求所需的时间远少于执行第一个请求所需的时间。结果,第二个请求的响应先于第一个请求返回。子 select 已被填充,用户正准备在其中选择一些内容,此时,第一个请求的响应返回,并且 select 被填充了无效的当前值。结果,子 select 中的值与父 select 中的选择不匹配。我不知道这是否可以被视为一个 bug。但是,我们必须自己解决这种情况。
  • 为了进行客户端验证、控制请求优先级以及显示和隐藏动画 GIF 而不是“加载”消息,我们需要使用 populatingpopulated 客户端事件。再次强调,为了清晰起见:我们必须订阅以子 select 作为目标元素的 CascadingDropDownBehavior 的事件,但这些事件将在用户更改父 select 中的选择时引发。
  • 由于需要指定 CascadingDropDownCategory 属性,因此(虽然不是必需的)最好使用 CascadingDropDown 来填充顶部的 DropDownList,而不是经典的绑定。
  • CascadingDropDown 在 Web 窗体中的位置(因此,它们的初始化顺序)起着重要作用。填充父 selectCascadingDropDown 应该首先被初始化(并放置)。

最后,关于我一开始提到的那两个辅助方法。

  • ParseKnownCategoryValuesString 方法将 knownCategoryValues Web 方法参数从 String 解析为 StringDictionary。这非常有用。
  • QuerySimpleCascadingDropDownDocument 方法可以帮助您根据现有的类别/值 StringDictionary 从数据集中检索 CascadingDropDownNameValue 对象集合。我非常不喜欢数据集,所以我不用这个方法。但如果您喜欢它们,可以查看这个教程
© . All rights reserved.