DropDownCheckList ASP.NET 服务器控件






4.86/5 (31投票s)
2005年10月18日
10分钟阅读

394064

9356
CheckBoxList 的子类,它呈现为一个自定义的下拉列表,其行为通过客户端 JavaScript 定义。
引言
对于最终用户而言,DropDownCheckList
类似于一个 HTML <select>
框,单击时会显示带有复选框的可用选项。显示框会列出选中的选项,并带有开发人员定义的列表分隔符,以及在列出的选项超出显示宽度时附加省略号或其他文本的功能。本文介绍了该控件的常用属性和方法,并描述了选择 CheckBoxList
子类化的原因(从中获得的益处)以及因此面临的基本问题。还介绍了通过客户端 JavaScript 解决的挑战,包括在必要时显示省略号,以及使用文档单击处理程序在列表外部单击时自动折叠列表。后者和借用的“shim”技术在开发自定义下拉 Web 控件时尤其有用。
使用控件
DropDownCheckList
的设计包含一个客户端库,旨在提供跨平台兼容性,至少对于主流浏览器的现代版本是如此。它已在 Internet Explorer 6.x、Netscape 7.x 和 8.x 以及 Firefox 1.x 中进行了专门测试。JavaScript 文件 DropDownCheckList.js 包含 DropDownCheckList
的客户端对象定义。要使用该控件,请先将文件 DropDownCheckList.js 复制到以下目录
wwwroot\aspnet_client\UNLV_IAP_WebControls\DropDownCheckList
有多个属性可用于自定义控件的显示。使用 DisplayBoxCssClass
和/或 DisplayBoxCssStyle
属性来指定显示框的 CSS 样式。同样,DisplayTextCssClass
和 DisplayTextCssStyle
指定显示文本的 CSS 样式,而 CheckListCssClass
和 CheckListCssStyle
表示复选框列表框的 CSS 样式。
显示框中下拉图像的渲染取决于 DropImageSrc
和 DropImagePosition
属性。如果 DropImageSrc
为空,或者 DropImagePosition
设置为 NoImage
,则下拉框中不会显示图像。如果指定了 DropImageSrc
,则图像的位置将是 Left
、Right
或 Both
,由 DropImagePosition
指示。
带有各种 CSS 样式和下拉图像的 DropDownCheckList
控件的行为由多个附加属性定义。DropDownMode
属性决定复选框列表的显示方式。如果设置为 Inline
,则复选框列表将在周围的 HTML 中展开。如果设置为 OnTop
或 OnTopWithShim
,则复选框列表将绝对定位在其他 HTML 内容之上。“shim”选项是为了与 Internet Explorer 兼容而存在的;有关更多详细信息,请参见下面的 使用 Shim 兼容 Internet Explorer。
Separator
属性指示用于分隔列表项的字符。DisplayTextWidth
指定显示框中用于列出选定选项的最大像素宽度。当列出的选项超出此宽度时,文本将被截断,并附加 TruncateString
属性。通常,TruncateString
是一个省略号(…),但也可根据需要设置为其他文本或空字符串。如果您希望显示框扩展而不是截断文本,请将 DisplayTextWidth
设置为 -1。
要指示未选中任何选项时要显示的文本,请将 TextWhenNoneChecked
属性设置为所需的字符串。您还可以通过将 DisplayTextList
属性设置为 Labels
或 Values
来指定是在显示框中列出复选框标签还是值(分别是子 ListItem
的 Text
和 Value
属性)。
DisplayTextList
属性设置为 Values
作为 CheckBoxList
的子类,数据绑定属性(如 DataSource
和 Items
集合)是继承的。在表单提交时,开发人员可以检查 Items
集合中选中的复选框,就像处理 CheckBoxList
一样。DropDownCheckList
还公开了两个重载的实用方法:SelectedLabelsToString()
和 SelectedValuesToString()
。每个方法都会返回选中的项,并将其列为一个单一的连接字符串。这些重载允许开发人员指定列表分隔符和文本分隔符。
以下是一个使用 DropDownCheckList
控件的完整 .aspx 页面的示例,演示了几个属性和两个 Selected... ToString()
方法
<%@ Page Language="c#" AutoEventWireup="true" %>
<%@ Register TagPrefix="cc1"
Namespace="UNLV.IAP.WebControls"
Assembly="DropDownCheckList" %>
<script runat="server">
void Page_Load(object o, EventArgs e)
{
lblResults.Text = "";
}
void btnSubmit_Click(object o, EventArgs e)
{
string sLabels = dd.SelectedLabelsToString(", ");
string sValues = dd.SelectedValuesToString(", ", "'");
lblResults.Text = "Selected Items: " + sLabels
+ "<br />"
+ "Values: " + sValues;
}
void btnClear_Click(object o, EventArgs e)
{
dd.SelectedValue = null;
}
</script>
<html>
<head>
<title>DropDownCheckList Sample</title>
<style>
.boxStyle
{
border : 2px solid darkBlue;
background-color : lightBlue;
padding : 8px;
}
</style>
</head>
<body>
<form runat="server">
<h3>DropDownCheckList Sample</h3>
<p>Click the drop-down box to select options</p>
<cc1:DropDownCheckList id="dd" runat="server"
RepeatColumns = "2"
DropImageSrc = "dropImage.gif"
DropImagePosition = "Right"
DropDownMode = "OnTopWithShim"
CheckListCssClass = "boxStyle"
CheckListCssStyle = ""
DisplayTextCssStyle = "font-family: Tahoma;"
DisplayTextWidth = "180"
DisplayTextList = "Labels"
Separator = ", "
TruncateString = "..."
TextWhenNoneChecked = "--select--"
>
<asp:ListItem text="North" value="N" />
<asp:ListItem text="South" value="S" />
<asp:ListItem text="East" value="E" />
<asp:ListItem text="West" value="W" />
<asp:ListItem text="Northeast" value="NE" />
<asp:ListItem text="Southeast" value="SE" />
<asp:ListItem text="Northwest" value="NW" />
<asp:ListItem text="Southwest" value="SW" />
</cc1:DropDownCheckList>
<p>
<asp:Button id="btnSubmit" runat="server" text="Submit Choices"
onClick="btnSubmit_Click" />
<asp:Button id="btnClear" runat="server" text="Clear Choices"
onClick="btnClear_Click" />
</p>
<p><asp:Label id="lblResults" runat="server" /></p>
</form>
</body>
</html>
继承自 CheckBoxList
子类化 CheckBoxList
有积极和消极的后果。积极的一面是,我们的 DropDownCheckList
继承了 CheckBoxList
内置的数据绑定支持、其 Items
集合以及其重复布局属性。
消极的一面是,我们无法访问单个 CheckBox
控件,而这是我们添加 `value` 属性(当 DisplayTextList
设置为 Values
时需要)和客户端 onclick
处理程序所必需的。简单来说,CheckBoxList
不将 CheckBox
控件作为子项,因此我们无法像这样操作单个框。我们可以在客户端库中添加一个 onclick
处理程序,但向渲染的复选框添加 `value` 属性是必须在服务器端管理的。
为了在这一限制下工作,同时又能利用 CheckBoxList
的数据绑定和重复布局渲染,我们在重写的 Render()
方法中执行了一个技巧。在生成显示框所需的 HTML 后,我们渲染一个用于复选框列表的 <div>
标签。如果不需要渲染 `value` 属性,我们只需将基类 CheckBoxList
的 Render()
方法应用于输出流。但如果我们确实需要渲染值,那么我们将在一个自定义的 HtmlTextWriter
上执行 base.Render()
,该 HtmlTextWriter
使用 StringWriter
而不是普通的输出流构建。这样,我们就可以检索渲染的列表作为一个普通的 string
,并对其进行操作以插入我们自己的 `value` 属性。Render()
中与此相关的代码如下所示
protected override void Render(HtmlTextWriter output)
{
. . .
// next, render the contents of the checkboxlist;
// do we need to render values?
if (_displayTextList == DisplayTextListEnum.Values)
{
// if so, we need to render to a string first
// so we can include our own values attribute
StringWriter sw = new StringWriter();
HtmlTextWriter wr = new HtmlTextWriter(sw);
base.Render(wr);
string sHtml = sw.ToString();
wr.Close();
sw.Close();
// now modify the code to include custom attributes
sHtml = ModifyRenderedCheckboxes(sHtml);
// and write to the output stream
output.Write(sHtml);
}
else
{
// if we're not rendering custom value attributes,
// just output the checkboxes to the output stream
base.Render(output);
}
. . .
}
上面引用的 ModifyRenderedCheckboxes()
方法使用正则表达式来定位每个复选框,然后将适当的 ListItem.Value
属性插入为 `value` 属性。
protected string ModifyRenderedCheckboxes(string sHtml)
{
// use a regular expression to identify in the form:
//<input id="DropDownCheckList1_0" type="checkbox"
string s = string.Format("input id=\"{0}_(?<" +
"index>\\d+)\"\\s+type=\"checkbox\"\\s+",
this.ClientID);
Regex r = new Regex(s, RegexOptions.IgnoreCase);
MatchCollection matches = r.Matches(sHtml);
foreach (Match m in matches)
{
int index = Convert.ToInt32(m.Groups["index"].Value);
sHtml = Regex.Replace(sHtml, m.Value, m.Value + " value=\""
+ this.Items[index].Value + "\" ");
}
return sHtml;
}
客户端 JavaScript 中解决的挑战
该控件的驱动目标之一是保持跨平台兼容性,至少与 Internet Explorer (6.x)、Netscape (7.x 和 8.x) 和 Firefox (1.x) 的近期版本兼容。这一目标影响了客户端脚本的几个方面。
(关于 Netscape 6.x 的说明:由于一个重要原因,本文提供的控件无法在 Netscape 6 中运行。Netscape 6 不会提交设置为 display
为 none
的 <div>
中的表单输入——因此当复选框列表折叠时,其复选框选择不会与其他表单输入一起提交。希望支持 Netscape 6 的开发人员可能希望尝试使用 visibility
属性来代替 display
属性。
当显示文本过宽时附加省略号
由于要列出的选定选项可能比显示的宽度更多,因此在截断文本后附加一个指示符很有用。通常,在这种情况下会使用省略号(…)。
Internet Explorer 支持 text-overflow
[^] 样式属性,顾名思义,它可以设置为 ellipsis
。不幸的是,这不是标准化的,并且在 Firefox 和 Netscape 中不受支持。此外,IE 只支持省略号——当字符串被截断时,您无法自定义附加的文本。
为确保对 Firefox 和 Netscape 的支持,并允许自定义 TruncateString
属性,我们在客户端脚本文件中使用原型函数 DisplayCheckedItems()
,其定义如下
function DDCL_DropDownCheckList_DisplayCheckedItems()
{
var sLabel = "";
var sCurrent = "";
var sFull = "";
var sBefore = "";
var sCompText = "";
var bEllipsisAdded = false;
// get all checkboxes in the checklist
var e = this.divCheckboxes.getElementsByTagName("input");
// clear the display text
this.divText.innerHTML = "";
// clear the title (tooltip) attribute
this.divDisplayBox.title = "";
// loop through all checkboxes in the checklist to see
// which ones are checked;
for (var i=0; i<e.length; i++)
{
if (e[i].type == "checkbox" && e[i].checked)
{
// if the checkbox is checked, get its associated label text
if (this.displayList == DDCL_DISPLAYTEXTLIST_LABELS)
// get the label for the checkbox
sLabel = this.GetLabelForCheckbox(e[i]);
else
// get the value for the checkbox
sLabel = e[i].value;
// add the list separator if necessary
if (sCurrent != "")
{
sCurrent += this.separator;
sFull += this.separator;
}
sFull += sLabel;
sCurrent += sLabel;
if (bEllipsisAdded == false)
{
// add this one to the text box, then test for the
// width against the display box
this.divText.innerHTML = "<nobr>" + sCurrent + "</nobr>";
if (this.divText.offsetWidth > this.boundingBoxWidth
&& this.allowExpand == false)
{
// too big; shrink by what we can and add the ellipsis
// (or other trunacte string)
while (this.divText.offsetWidth > this.boundingBoxWidth
&& sCurrent.length > 0)
{
sCurrent = sCurrent.substr(0, sCurrent.length - 1);
this.divText.innerHTML = "<nobr>" + sCurrent
+ this.truncateString + "</nobr>";
}
// and indicate the ellipsis (or other truncate text)
// has been added
bEllipsisAdded = true;
}
}
}
}
// finally, if there are no contents, display the textWhenNone message
if (this.divText.innerHTML == "")
{
if(this.textWhenNone == "")
this.divText.innerHTML = " ";
else
this.divText.innerHTML = this.textWhenNone;
}
// if we added the ellipsis, set the title attribute to the full string
// (which will display as a tooltip in most browsers)
if (bEllipsisAdded)
this.divDisplayBox.title = sFull;
else
this.divDisplayBox.title = "";
}
对于每个选中的框,其标签或值(取决于服务器端属性 DisplayTextList
)都会添加到显示文本 <span>
元素中。这会导致 <span>
的 offsetWidth
属性增加,该属性与包含显示文本 <span>
的嵌套元素(<div>
)的 offsetWidth
进行比较。在构造函数中,我们之前已将边界框的 offsetWidth
存储在 boundingBoxWidth
属性中。
此脚本还引用了 GetLabelForCheckbox()
函数,该函数用于确定与已渲染的 <input type=”checkbox”>
对应的 <label>
文本,以用于选中的选项。
function DDCL_DropDownCheckList_GetLabelForCheckbox(elem)
{
var e = this.divCheckboxes.getElementsByTagName("label");
for (var i=0; i<e.length; i++)
{
if (e[i].htmlFor == elem.id)
{
for (var j=0; j<e[i].childNodes.length; j++)
{
if (e[i].childNodes[j].nodeType == 3) //text type
{
return e[i].childNodes[j].nodeValue;
}
}
}
}
// still here? no <label> for this checkbox then
return null;
}
允许单击列表框外部以折叠列表
单击显示框会展开复选框列表。在我创建该控件的第一个版本中,需要再次单击显示框才能将其折叠。这在初步测试时很快就成了令人烦恼的事情,因为我习惯于单击复选框列表旁边的空白区域,期望(下意识地?)列表能自行折叠。也许这只是我的固执,但对于该控件来说,在列表外部单击时自动折叠列表的功能似乎很合适。
为了实现这一点,客户端构造函数向文档添加了一个 click
事件处理程序。它使用 document.attachEvent()
(在 IE 中支持)或 document.addEventListener()
(在 Mozilla 中支持),以维护任何现有的文档 click
事件处理程序。构造函数中的相关代码如下
function DDCL_DropDownCheckList(...)
{
. . .
// if the browser supports bubbling events, install a default click
// handler for the document too, that will close the checkboxes div
// if there is a click outside it
if (document.attachEvent)
{
document.attachEvent('onclick'
, function() { eval("DDCL_HandleDocumentClick('" + id + "');") }
);
}
else if (document.addEventListener)
{
document.addEventListener('click'
, function() { eval("DDCL_HandleDocumentClick('" + id + "');") }
, false);
}
. . .
}
以这种方式添加到 document
的事件会以冒泡方式触发,这意味着 document
是最后一个处理该事件的对象。如果单击了我们的复选框之一,它将在文档获取事件之前获得处理该事件的机会。这非常有用,因为它使我们能够指示已在复选框 div
中发生了单击,并且 document
处理程序应忽略该单击。
复选框 div
的 click
处理程序会将 inCheckboxDiv
属性设置为 true。然后,我们的自定义 document.click
处理程序会测试此属性,以确定是否应折叠复选框列表。
function DDCL_HandleDocumentClick(id)
{
var obj = DDCL_GetObject(id);
if (obj)
{
if (obj.inCheckboxDiv == true)
obj.inCheckboxDiv = false;
else
obj.CloseCheckList();
}
}
使用 Shim 兼容 Internet Explorer
绝对定位在其他控件之上的自定义 <div>
部分会展示 Internet Explorer 中一种相当不幸但记录在案的行为。任何窗口控件,包括普通下拉列表和 ActiveX 控件,都会显示在 <div>
之上,并用它们自己的内容覆盖 <div>
的内容。这发生在不考虑 <div>
中使用的 z-index
。
Internet Explorer 中的 <select>
框会透过 <div>
显示.
一种常见的解决方法是使用 JavaScript 循环遍历文档中的所有 <select>
对象,将每个对象的 display
设置为 none
,或将每个对象的 visibility
设置为 hidden
。我对此解决方案从未满意,因为它可能导致最终用户出现一些相当奇怪的显示行为。
幸运的是,Joe King,为 Coalesys, Inc. 工作,开发并发布了另一种解决方案来解决此问题,该解决方案适用于 Internet Explorer 5.5 及更高版本 [1]。该技术使用一个“shim”,形式是一个 <iframe>
,其 z-index
比 <div>
的 z-index
小一。窗口控件仍会显示在 <div>
之上,但从 5.5 版开始,绝对定位的 <iframe>
会显示在窗口控件之上。Joe 提出的解决方案是让一个 shim <iframe>
显示在任何窗口控件之上,并将下拉列表 <div>
叠加在该 <iframe>
之上。
如果 DropDownCheckList
的 DropDownMode
设置为 OnTopWithShim
,它将渲染这样一个 <iframe>
。然后,通过客户端原型方法 OpenCheckList()
,shim 将与下拉列表 <div>
一起显示
function DDCL_DropDownCheckList_OpenCheckList()
{
// open the checkboxlist; first, position it below the displaybox
// determine the position based on the dropDownMode
if (this.dropDownMode == DDCL_DROPDOWNMODE_INLINE)
{
// inline mode; we're already setup as we need to be
this.divCheckboxes.style.display = "block";
}
else
{
// on top modes; position the box
this.divCheckboxes.style.left = DDCL_findPosX(this.divDisplayBox);
this.divCheckboxes.style.top = DDCL_findPosY(this.divDisplayBox)
+ this.divDisplayBox.offsetHeight;
this.divCheckboxes.style.display = "block";
// if we want the shim, apply that now
if (this.dropDownMode == DDCL_DROPDOWNMODE_ONTOPWITHSHIM)
{
this.shim.style.width = this.divCheckboxes.offsetWidth;
this.shim.style.height = this.divCheckboxes.offsetHeight;
this.shim.style.top = this.divCheckboxes.style.top;
this.shim.style.left = this.divCheckboxes.style.left;
this.shim.style.zIndex = this.divCheckboxes.style.zIndex - 1;
this.shim.style.display = "block";
}
}
}
我还想感谢 Peter-Paul Koch [2],感谢他提供的非常实用的 findPosX(
)
和 findPosY()
函数。
摘要
DropDownCheckList
是一个 ASP.NET 控件,它将复选框作为选项呈现在自定义下拉列表中。虽然服务器端公开了多个用于定义行为和样式的属性,但用户与控件的交互通过客户端 JavaScript 库进行管理。这包括在列表选项的宽度超出显示框时应用省略号或其他文本,以及用户单击列表框外部时自动折叠复选框列表。使用文档事件处理程序来自动折叠,并结合 Internet Explorer 兼容性的 shim 技术,提供了旨在改善自定义下拉列表用户体验的技巧示例。
致谢
- Joe King, Coalesys, Inc., 如何用 DHTML 层覆盖 IE 窗口控件(Select Box、ActiveX Object 等)[^]
- Peter-Paul Koch[^]