代码改变世界

使用AJAX扩展器自定义控件

2008-04-17 10:17  Valens  阅读(822)  评论(1编辑  收藏  举报
By Dino Esposito

输入控件在任何应用程序中都必不可少,但是当 Web 应用程序代表您组织的形象时,它们显得尤为重要。这些控件可能是用户评判您的应用程序甚至您组织可信度的主要衡量标准。
在 Windows® 和 Web 编程中,用户界面都是通过创建控件来构建的。但是可用的控件并不是特别丰富。Windows 窗体的输入控件仍以 Win32® 控件为基础,Web 控件则比 HTML <INPUT> 标记的包装好不了多少。很明显,我们需要更好的输入控件,尤其对编写 ASP.NET AJAX 应用程序的开发人员更是如此,因为这些控件非常依赖客户端用户的交互。
在这个分为两部分的系列文章中,我将主要介绍 ASP.NET 3.5 的输入功能,但是我要说明的大部分信息也适用于安装了 ASP.NET AJAX Extensions 的 ASP.NET 2.0。我将讨论如何通过使用 AJAX 控件工具包(在此我称之为 ACT)中提供的某些 JavaScript 驱动的行为,增强 ASP.NET 输入控件功能。本月我将介绍一组基本的 ACT 输入扩展;下个月我将深入介绍更多的高级功能。

入门
ACT 是 Web 控件的共享源码库,您可从 codeplex.com/AtlasControlToolkit 下载它。ACT 通过预定义的脚本代码块扩展了现有 ASP.NET 服务器控件。ACT 扩展器可应用于内置和自定义的 ASP.NET 控件。
从技术层面来讲,ACT 组件在控件(例如 TextBox 和 Panel)之上添加了额外的 JavaScript 层。因此,对 AJAX 的使用并不是必需的。但是,ACT 组件的实现在很大程度上依赖于 Microsoft® AJAX Library 中的 JavaScript 功能,以及 ASP.NET AJAX Extensions 服务器运行时。
ACT 授权规范位于 asp.net/ajax/ajaxcontroltoolkit。CodePlex 上的 ACT 项目页有两个专栏下载项:带 AJAX Extensions 的 ASP.NET 2.0 和 ASP.NET 3.5(请参阅 codeplex.com/AtlasControlToolkit/Release/ProjectReleases.aspx)。
要将 ACT 用于 ASP.NET 应用程序中,您需要在项目中引用该程序集,然后将 ACT 注册到使用它的任何页面。对每个页面注册 ACT 的方法是使用 @Register 指令并指定任意前缀,例如:
  <%@ Register Assembly="AjaxControlToolkit" 
               Namespace
="AjaxControlToolkit" 
               TagPrefix
="act" %>
此外,您也可以为应用程序中的所有页在 web.config 文件中注册该库。以下是要使用的配置脚本:
  <pages>
     
<controls>
        
<add tagPrefix="act" 
             
namespace="AjaxControlToolkit" 
             assembly
=" AjaxControlToolkit" />
     
</controls>
  
</pages>
ACT 控件不会自动出现在 Visual Studio 工具箱中,但是添加新的选项卡非常容易,如图 1 所示。
 
Figure 1 ACT in Visual Studio 2008 

扩展控件的功能
在面向对象的框架(例如 ASP.NET)中,每个服务器控件都是可以进一步自定义(从其派生新控件)的类。例如,假设您需要一个屏蔽的文本框控件。首先可以创建一个从内置 TextBox 类派生而来的新 MaskedTextBox 类。在该派生类中可以添加一些属性和客户端功能,这些是提供屏蔽输入字段所需的行为类型必需的。如果还需要数字文本框或向上/向下输入框,则可以创建另一个自定义控件类。但是为每一个额外的功能创建新控件并不是明智之举。如图 2 所示,最终结果是大量控件形成一种平坦的层次结构,在此每个新控件都在同一基类上增加了一点点行为。

Figure 2 
幸运的是,另外还有一个增强控件的方法:即扩展现有控件的特定实例的行为。在这种情况下,附加的行为可通过 ASP.NET 扩展器来添加。给定控件的实例可以与任意数量的附加行为关联,从而增强其功能而无需创建新的类。
作为 ASP.NET 服务器控件,扩展器获得对已扩展控件的引用,并通过插入脚本代码修改其行为。该扩展器控件可能具有一些它自己的属性,所以开发人员还可以进一步配置行为。目标 ASP.NET 控件及其扩展器集之间的关联通常在页面标记中以声明方式确立。以下代码段显示了如何将数字向上/向下界面添加到文本框,这样用户只需单击向上/向下按钮便可选择数字:
<asp:TextBox ID="Children" runat="server" />
<act:NumericUpDownExtender ID="UpDown1" runat="server"
     TargetControlID
="Children" 
     Width
="100" />
如您所见,该页包含了一个常规的 TextBox 控件和一个 NumericUpDownExtender 控件的实例。扩展器上的 TargetControlID 属性设置了被扩展控件的 ID。Width 属性特定于扩展器,并且定义了被扩展控件的最终宽度以及扩展器提供的任何附加 UI 元素——在本例中是一对向上/向下按钮(请参见图 3)。扩展器的属性可以在代码隐藏类中以声明方式和编程方式进行设置。

Figure 3 NumericUpDown Extender in Action 

扩展器控件分析
ACT 包含可为特定用户界面需要提供服务的典型服务器控件,以及可将特定行为添加到现有控件的扩展器。例如 ACT 中存在 TabContainer 控件和 ReorderList 控件,前者可设置基于选项卡的界面,后者为列表框提供了拖放支持,以便移动项目。但是,大多数 ACT 都是由扩展器组成的。
请看一下图 4,从中您会发现扩展器继承自一个基类,即在 AJAX Extensions 程序集中定义的 ExtenderControl。在内部,自定义扩展器控件类会覆盖 GetScriptReferences 方法(返回要插入到客户端页面中以实现行为的脚本文件列表)和 GetScriptDescriptors 方法(返回客户端属性和服务器端属性之间的映射列表)。如此一来,在客户端为给定属性分配的任何值就都会正确地反映在服务器上。除了这个相对标准的代码之外,扩展器无非是使用 DOM 为最终用户提供更多 UI 元素的 JavaScript 代码。
图 4 显示的示例扩展器将焦点行为添加到几乎所有 ASP.NET 控件;也就是说,添加到从 Control 派生而来的所有控件。焦点功能可在链接的 focusbehavior.js 文件中实现。此脚本文件可能会被嵌入到包含该扩展器控件的程序集中,然后在要求时被下载到客户端。focusbehavior.js 文件使用 Microsoft AJAX Library 的编程模型(请参阅我在 2007 年 12 月的专栏 msdn.microsoft.com/msdnmag/issues/07/12/CuttingEdge),并重点围绕可为焦点和模糊事件注册文档对象模型 (DOM) 处理程序的初始化方法。在焦点处理程序中,代码会设置用于目标元素的 CSS 类。在模糊处理程序中,代码会重置 CSS 类。有关详细信息,请查看本专栏随附的源代码。全盘考虑之后,创建扩展器最简单的方法是使用 ACT 的功能。运行工具套件下载中的安装程序、设置模板,然后选择“添加 AJAX 控件扩展器”即可获得扩展器和行为的基本框架。有关详细信息,请访问 asp.net/AJAX/AjaxControlToolkit/Samples/Walkthrough/CreatingNewExtender.aspx

输入扩展器
正如我提到的那样,ACT 的特点是具有好几个扩展器,它们可将功能添加到按钮、文本框、面板、列表框等。ACT 中的组件可分成几个类别:面板、输入、通用 UI 和动画。带文档和示例代码的完整组件列表位于 asp.net/ajax/control-toolkit,ACT 中的输入扩展器的完整列表如图 5 所示。

水印和输入
网页上最恼人的元素之一是不可标记的文本框或标签和文本框不对齐的页面。这可能是因用户选择的字体大小或其他设置引起的,但结果是用户无法确定哪些内容属于该文本框。有一个不错的解决方案,那就是文本水印,即当用户开始在文本框中键入文字时会从文本框中消失的默认文本(请参见图 6)。以下代码显示了获得此效果的方法:

Figure 6 Some Input Extenders (单击该图像获得较大视图)
<act:TextBoxWatermarkExtender runat="server" ID="TextBoxWatermark1"
     TargetControlID
="TextBox1" 
     WatermarkText
="Type Last Name Here" 
     WatermarkCssClass
="watermarked" />
该扩展器插入了处理焦点、模糊和 keydown 事件的脚本代码。当控件获得焦点时,水印就消失了。当控件失去焦点时,如果没有输入文本,水印会重新出现。当页面回发时将清除水印文本。这可通过使用 DOM 的提交事件的处理程序实现。水印扩展器控件允许您指定文本和适用它的 CSS 样式。您可以使用特殊的 CSS 样式将常规输入文本和水印文本从视觉上区分开来。
在 HTML 中,有一个用来接收数据的主要元素,即 <input> 元素。您必须将它用于数字、字符串、日期、货币值等等。您通常需要将数字输入限制在某个特定的值范围内。当然,您可以通过一个验证层来强制执行此业务规则,但是直接在输入框中处理会更好。
Slider 和 NumericUpDown 扩展器可强制用户只输入给定范围内的数字。Slider 扩展器会隐藏其相关的 TextBox 并用图形滑块代替它,该图形滑块可在最小和最大间隔内以不连续步骤移动。通过编程方式总是可以将底层 TextBox 设置为您想要的任何值。但是请注意,分配的值必须在通过滑块设置的数字范围内;否则,滑块会自行修复它。让我们看一下下面的示例:
<act:SliderExtender runat="server" ID="SliderExtender1"  
     TargetControlID
="Income" 
     Minimum
="0" 
     Maximum
="200000" 
     Steps
="41" 
     BoundControlID
="IncomeAsText" />
该滑块应用于名为 Income 的 TextBox 控件,并确保它会可视化地获得一个 0-200000 范围内的值。Steps 属性指示应提供多少个不连续的步骤。上述设置允许该滑块每次跳 5000。如果您以编程方式在服务器上设置了滑块,则该值首先会被转换成数字,然后再被映射到最接近的步骤值。如果传入字符串,滑块会忽略它,并默认为范围的初始值。如果您通过 JavaScript 在客户端设置了底层文本框的值,那么该值就会在下一次回发时被正确识别,但是它不会立即反映在用户界面上。若要从客户端以编程方式更改滑块中的值,则使用以下代码:
$find("SliderExtender1").set_Value(145678);
为什么必须选择 $find,而不是 $get?$get 函数是 document.getElementById 的简写。这样它只能查找 DOM 元素。$find 函数代表 Sys.Application.findComponent,可应用于 Microsoft AJAX Library 中使用编程方式创建的任何组件。可使用以下脚本将扩展器添加到页面:
Sys.Application.add_init(
  function() {
      $create(AjaxControlToolkit.SliderBehavior, 
           {
"BoundControlID":"IncomeAsText",
            
"Maximum":200000,
            
"Steps":41,
            
"id":"SliderExtender1"}, 
           
null
           
null
           $
get("Income"));
  }
);
作为 Microsoft AJAX Library 组件,它只能通过 $find 函数找到。
BoundControlID 属性是指动态显示当前滑块值的 DOM 元素。大多数情况下,您会为此使用 <span> 或 Label。在内部,滑块可以区分 INPUT 和任何其他 HTML 元素。在前一个示例中它设置了值属性;否则它使用与 DOM 元素相匹配的 innerHTML 属性。
因为滑块脚本隐藏了底层 TextBox,所以元素在页面加载过程中可能会闪烁。您可使用 CSS 样式以声明形式隐藏它。最后,请注意常规文本框以内嵌形式显示,而滑块框则是已定位的块。
NumericUpDown 扩展器可在可能值的受约束列表中选择下一个/上一个值。虽然 NumericUpDown 的名称表示它与数字有关,但它不仅仅是指定数字量的工具。RefValues 属性可让您列出移动时经过的值:
<act:NumericUpDownExtender ID="UpDown1" runat="server"
     Width
="120" 
     RefValues
="None;1;2;3;4;5;More than 5;" 
     TargetControlID
="Children" />
NumericUpDown 可以从远程服务中检索引用值。每当用户单击向上或向下按钮时,就异步调用该服务。有一点一定要记住,那就是该服务必须是可调用脚本,并且包括具有以下签名的方法:
public int YourMethod(int current, string tag) 
您可将扩展器配置成通过 ServiceDownPath、ServiceDownMethod、ServiceUpPath 和 ServiceUpMethod 属性使用远程服务。最后,向上和向下按钮都不必通过脚本自动生成。它们可以是在页面中已定义好的按钮,并可通过 TargetButtonDownID 和 TargetButtonUpID 属性进行引用。

确认和模态
当用户有机会确认其意图之后,最好启动可收集数据以运行关键服务器程序的输入窗体。例如,在永久删除服务器上的某些数据之前实现一个确认步骤是比较明智的做法。客户端确认步骤只能通过 JavaScript 来实现,方法是弹出类似模式对话框
<act:ConfirmButtonExtender ID="ConfirmButtonExtender1" runat="server"
     TargetControlID
="Button1" 
     ConfirmText
="Are you sure you want to 
                 "send this data? Really sure?" />
该扩展器挂接了已绑定按钮控件(不只是 Button,还有 LinkButton 和 ImageButton)的单击事件,并会弹出警告消息框。如果您取消该消息,则不会发生回发。浏览器的 window.alert 方法可用来显示该消息。显然,您可以手动编写同一功能的代码,而无需使用扩展器,但是扩展器相对简单一些,且可重用程度更高。
如果需要更丰富且更具自定义特征的用户界面,那么就必须另辟蹊径。ModalPopup 扩展器是值得探讨的另一种方案。ModalPopup 将模态添加到一段标记中——通常是面板。通过绑定到按钮控件,它可以弹出指定的 DIV 标记,并禁用底层页。除非通过它提供的用户界面,否则最上面的 DIV 标记中的元素无法消除。单击除最上层 DIV 中的元素以外的任何元素都会失败,永远也达不到想要的目标。ModalPopup 添加了一个不可见的 DIV 来涵盖整个浏览器窗口,这是一个聪明的技巧。这一层会截取所有用户操作,并阻止其到达底层控件。凭借聪明的 CSS 编码,您可以增加很多有用的效果,例如将最上面的 DIV 下的任何元素变灰(请参见图 7)。

Figure 7 ModalPopup Extender (单击该图像获得较大视图)
在以下的 ModalPopup 扩展器代码中,PopupControlID 属性直接引用即将作为弹出窗口的标记元素:
<act:ModalPopupExtender ID="ModalPopupExtender1" runat="server"
     TargetControlID
="LinkButton1"
     PopupControlID
="Panel1"
     BackgroundCssClass
="modalBackground"
     OkControlID
="Button1"
     OnOkScript
="yes()"
     OnCancelScript
="no()"
     CancelControlID
="Button2" />
在大多数情况下,此元素可能是 ASP.NET 面板控件或普通 DIV 元素。BackgroundCssClass 属性指出在模式对话框存在期间要应用于基础页的 CSS 样式。要获得图 7 所示的灰显效果,应使用下列属性:
.modalBackground 
{
    background-color
:Gray;
    filter
:alpha(opacity=70);
    opacity
:0.7;
}
几乎所有的模式对话框都有一对“确定”和“取消”按钮。这些按钮可以是链接按钮、下压按钮或图像按钮,也可以使用任何文本标记。您可使用 OkControlID 和 CancelControlID 属性告诉扩展器,面板中的哪些控件代表“确定”和“取消”按钮。同样,用 OnOkScript 和 OnCancelScript 属性可以指示 JavaScript 函数在用户单击确定或取消模式对话框时运行。

增强的复选框
另一个可以帮助用户输入正确的数据,并为系统省去某些额外客户端验证步骤的扩展器是 MutuallyExclusiveCheckBox。它允许定义复选框的逻辑组,以使用户在每个组中只可选择一个选项:
<asp:CheckBox runat="server" ID="beginner" Text="Beginner" />
<asp:CheckBox runat="server" ID="intermed" Text="Intermediate" />
<act:MutuallyExclusiveCheckBoxExtender runat="server" ID="Mutual1"
     TargetControlID
="beginner"
     Key
="Expertise" />
<act:MutuallyExclusiveCheckBoxExtender runat="server" ID="Mutual2"
     TargetControlID
="intermediate"
     Key
="Expertise" />
上述扩展器定义了一组两个互斥的复选框,名为 Expertise。通常情况下,用复选框可允许进行多项选择,而使用单选按钮可强制用户只能进行单项选择。那这里为什么不使用 RadioButtonList 控件呢?呃,您可能已经注意到,RadioButtonList 控件最初时所有选项都未选中,但是一旦作出了选择,就再也无法返回到完全未选中任何选项的状态。换句话说,要取消选择一个选项,只有选择另一个选项,而且总有一个选项被选中。使用 MutuallyExclusiveCheckbox 扩展器的复选框组则不会有这个限制。
通常使用嵌入在浏览器中的位图来显示复选框。以编程方式更改位图并不容易,因为没有公开的公共属性。ToggleButton 扩展器隐藏了绑定的复选框元素,并使用一段动态生成的标记替代它,该标记模拟复选框的布局,不同点是它使用用户定义的一对位图表示选中和未选中状态:
<act:ToggleButtonExtender ID="ToggleButtonExtender1" runat="server"
    TargetControlID
="CheckBox1" 
    ImageHeight
="19"
    ImageWidth
="19"
    UncheckedImageUrl
="~/images/DontLike.gif" 
    CheckedImageUrl
="~/images/Like.gif" />
ToggleButton 扩展器不能应用于 CheckBoxList 控件;它只能与单个复选框配合使用。能够更改标准复选框的外观令人高兴。

受限输入
防止用户错误涉及到通过筛选不需要的字符、无效的表达式或错误类型的数据来控制输入。使用标准的 TextBox 控件,您可强制进行正确的输入,方法是在可以将该字符串转换成其他任何数据类型的服务器上验证它。凭借 AJAX,输入数据的类型可以得到更好的控制。例如,如果使用了日历扩展器,用户要键入除了日期以外的其他内容实际上是不可能的。虽然 ASP.NET 提供了服务器 Calendar 控件,但是日历扩展器真的不一样,因为它完全在客户端构建它的用户界面,完全在客户端工作,而且当用户通过导航查看月份和日期时根本不会生成回发。此外,如果给定的浏览器不支持 JavaScript,则会显示原有的文本框:
<act:CalendarExtender ID="CalendarExtender1" runat="server"
     TargetControlID
="Birth"
     Format
="dd/MM/yyyy" />
上述代码段足以显示弹出式日历,因为相关的文本框会收到焦点。或者也可以在用户单击页面按钮时显示弹出窗口。该按钮的 ID 可通过 PopupButtonID 属性进行设置。Format 属性指示日期的格式,因为当用户取消弹出式日历时它会被写入文本框(请参见图 8)。

Figure 8 Calendar Extender 
FilteredTextBox 扩展器可防止用户将无效字符输入文本框。它主要是添加了可挂接键盘活动并筛选无用击键的 JavaScript。您可将扩展器配置成拒绝某些字符或确保只接受指定的字符。可使用 FilterMode 属性进行此设置。它确定要应用的筛选器类型——仅数字、仅小写或大写,以及自定义的字符集。应注意,带水印的 TextBox 无法筛选成只接受数值。实际上,筛选层会自动清除您设置的所有水印文本。

设计更出色的网页
若要为 ASP.NET 控件添加更多的行为,使用扩展器是一种强大而明智的方法。ACT 附带了 30 多种扩展器来丰富内置控件。在 ACT 中可以找到添加动画、拖放功能和自动完成的扩展器,以及更为重要的增强输入功能的扩展器。下载 ACT 时可带原始源代码,也可不带原始源代码。另外,大多数复杂的扩展器都具有交叉依赖性。例如,Calendar 扩展器需要 Popup 扩展器。
在本专栏中,我介绍了可使网页更有效的最简单且最常用的扩展器。在下一次的专栏中,我将继续介绍更高级的功能,例如屏蔽的编辑和自动完成。