ComboBox WebControl






4.72/5 (64投票s)
2003年7月14日
8分钟阅读

1108851

19982
IE ComboBox WebControl
引言
我一直想知道为什么微软,或者其他任何人都从未提供原生的 HTML ComboBox
元素。在网上研究了一番后,我决定自己动手。我想要一个在视觉上区别于默认 HTML Select
元素,并提供自动完成和列表验证等功能的控件。本文将详细介绍构成该控件的 JavaScript 编写的 View Link Behavior 和 C# 代码。
历史
此 Web 控件的 1.0 版本基于 Jeremy Bettis 在 http://www.deadbeef.com/ 编写的元素行为。虽然 Jeremy 的行为运行良好,但它不包含我想要的组合框的所有功能。此次更新是对该行为的全面重写,并解决了以下功能请求和 bug 修复。
ListItem
值检索 - 组合框的值现在映射到ListItem
(Option
) 的 value 属性。- 自动验证 - 您现在可以指定组合框将用户输入的文本与
ListItem
中的文本进行验证。 - 文本字段
OnChange
事件 - 文本字段的OnChange
事件会冒泡到组合框控件。 - Z 索引 - 下拉箭头将不再与其他 HTML 元素重叠。
- 设计器模式下的数据绑定支持。
- 与 HTML
Select
元素在视觉上的区别。 - 定位(Bug 修复)- 组合框现在可以正确打印或绝对定位。
- 第一个项目选择(Bug 修复)- 您现在可以选择下拉列表中的第一个项目。
使用 ComboBox 控件
注意:此控件是在 IE 6.0 下开发和测试的。但它可能兼容 IE 5.5,但不能与更旧版本或任何非微软浏览器一起使用。
对于所有新手,请仔细阅读并遵循说明!对于本节已解答的任何疑问,我将不再回复。
首先,我们需要将运行时文件,包括 combobox.htc 和 images 目录,复制到它们的默认虚拟目录 /$wwwroot/webctrl_client/progstudios/1_1/。您可以通过向 web.config 中的 appSettings
集合添加新元素来指定其他位置。
<appSettings>
<add key="PROGWEBCONTORLS_COMMONFILEPATH" value="MyVirtualPath" />
</appSettings>
源代码中包含的二进制和项目文件基于 .NET Framework 1.1 和 Visual Studio 2003。它们与 1.0 或 VS.NET 2002 不兼容,因此您需要自己重新编译程序集。要使用 VS.NET 2002 重新编译项目,请按照以下步骤操作
- 创建一个新的 C# "Web Control Library" 项目;将其命名为 "ProgStudios.WebControls" 或任何您喜欢的名称。
- 右键单击项目并选择 "Properties"。
- 确保 "Assembly Name" 和 "Default Namespace" 属性设置为 "ProgStudios.WebControls"。
- 将 ZIP 文件中的文件拖放到项目中。
- 右键单击位图图像,并在属性窗口中将 "Build Action" 属性设置为 "Embedded Resource"。
- 右键单击解决方案并选择 "Properties"。在 "Control Properties/Configuration" 下,确保配置设置为 "Release",然后按 "OK"。
- 右键单击项目并选择 "Build",或按 "ctrl+shift+b" 来构建项目。
- 输出的 DLL 将位于 bin/release 目录。
- 将控件添加到工具箱。
- 右键单击要放置控件的选项卡,然后选择 "Add/Remove Items"。
- 单击 "Browse" 并找到 DLL。
- 完成了!
要将程序集添加到 Web 项目,请右键单击解决方案资源管理器中的 "References" 并通过单击 "Browse" 来找到 DLL,或在 "Projects" 选项卡下添加项目。如果您不使用 Visual Studio,请确保将 DLL 复制到 bin 目录。安装控件后,您可以将控件从设计模式拖放到网页上。以下是生成的 HTML 代码。
第一行注册了程序集,并将 "cc1
" 标签前缀与程序集命名空间关联起来。"cc1:combobox
" 代表实际的控件。子元素 asp:ListItem
可以替换为传统的 <option>
标签。
<%@ Register TagPrefix="cc1" Namespace="ProgStudios.WebControls"
Assembly="ProgStudios.WebControls" %>
<html>
<body>
<cc1:combobox id="ComboBox1" runat="server">
<asp:ListItem>ComboBox</asp:ListItem>
</cc1:combobox>
</body>
</html>
您可以手动添加新项目,或者像在演示项目中一样,将其绑定到 DataView
。与 HtmlSelect
和 DropDown
控件一样,ComboBox
可以绑定到任何实现了 IListSource
、IList
或 IEnumerable
的实例。
<cc1:ComboBox id="ComboBox1" runat="server" DataSource="<%# dataView1 %>"
DataValueField="ProductID" DataTextField="ProductName">
...
protected DataView dataView1;
protected void Page_Load(object sender, System.EventArgs e) {
if (!this.IsPostBack) {
DataSet ds = new DataSet();
FileStream fs = new FileStream(
Server.MapPath("newdataset.xml"),
FileMode.Open,FileAccess.Read);
StreamReader reader = new StreamReader(fs);
ds.ReadXml(reader);
fs.Close();
dataView1 = new DataView(ds.Tables[0]);
ComboBox1.DataBind();
}
}
...
<prog:combobox id="ComboBox1" runat="server"/>
该控件还公开了一个服务器端事件 ServerChange
。您可以通过挂接 OnServerChange
事件处理程序来附加到该事件。如果在回发时值发生更改,将触发此事件。您还可以通过将 AutoPostBack
属性设置为 true
来指定控件自动回发。
<script runat="server" language="c#">
void ComboBox1_OnServerChange(object sender, System.EventArgs e) {
Msg.InnerText = "ServerChange Event Fired: Value=" + ComboBox1.Value;
}
</script>
....
<prog:combobox id="ComboBox1" runat="server"
AutoPostBack="true" OnServerChange="ComboBox1_OnServerChange">
AutoValidate
属性用于确保用户键入的文本与列表项集合中的文本匹配。如果不匹配,selectedIndex
将设置为 -1,值为一个空字符串。您可以通过设置 ErrorMessage
属性,在 ComboBox
失去焦点时显示错误消息。
在 C# Web 控件代码内部
为了获得一些免费功能,我决定继承自 WebControl
类。这个决定很容易,因为基类有一个 AttributesCollection
类成员。这很重要,因为所有 HTML 元素都支持 expando 属性,这些属性基本上是您可以附加到元素上的任意属性,并且可以通过客户端脚本访问。任何服务器控件无法识别的属性都将被添加到属性集合中,并在客户端呈现到根元素。
其余代码非常直接。除了公开 Web 客户端上可用的相应服务器端属性、方法和事件之外,我还必须实现某些方法来处理所有底层工作。控件必须参与回发以处理状态,因此,实现了 IPostBackDataHandler.RaisePostDataChangedEvent
和 IPostBackDataHandler.LoadPostData
接口方法来加载已发布的 قيمة(value)。
...
public virtual void RaisePostDataChangedEvent() {
this.OnServerChange(EventArgs.Empty);
}
public virtual bool LoadPostData(string postDataKey,
NameValueCollection postCollection) {
string sValue = this.Value;
string sPostedValue = postCollection.GetValues(postDataKey)[0];
if (!sValue.Equals(sPostedValue)) {
this.Value = sPostedValue;
return true;
}
return false;
}
...
我还必须重写 SaveViewState
和 LoadViewState
方法,以便在回发时管理 ListItem
。
...
protected override void LoadViewState(object savedState) {
if (savedState != null) {
object[] State = (object[])savedState;
this.Value = (string) State[0];
ArrayList ItemsList = (ArrayList) State[1];
foreach (object itemText in ItemsList) {
if (this.Items.FindByText((string)itemText)==null) {
ListItem item = new ListItem();
item.Text = (string) itemText;
this.Items.Add(item);
}
}
}
}
protected override object SaveViewState() {
ArrayList ItemsList = new ArrayList();
foreach (ListItem item in this.Items) {
ItemsList.Add(item.Text);
}
object[] savedState = new object[2];
savedState[0] = this.Value;
savedState[1] = ItemsList;
return savedState;
}
...
编写控件生成器类也非常直接。我继承了 ControlBuilder
并重写了 GetChildControlType
方法,只允许 ListItem
和 Option
子元素。
...
public override Type GetChildControlType(string tagName,
System.Collections.IDictionary attribs) {
string szTagName = tagName.ToLower();
int colon = szTagName.IndexOf(':');
if ((colon >= 0) && (colon < (szTagName.Length + 1))) {
// Separate the tagname from the namespace
szTagName = szTagName.Substring(colon + 1,
szTagName.Length - colon - 1);
}
if (String.Compare(szTagName, "option", true,
System.Globalization.CultureInfo.InvariantCulture) == 0 ||
String.Compare(szTagName, "listitem", true,
System.Globalization.CultureInfo.InvariantCulture) == 0 ) {
return typeof(System.Web.UI.WebControls.ListItem);
}
// No Type was found, throw an exception
throw new Exception(String.Format(
"Invalid child with tagname \"{0}\"", tagName));
}
ComboBox Link View Behavior
好的,现在我们来看看文章中最有趣的部分。本节将在一定程度上揭示我朋友称之为网页开发黑魔法的行为编程。当前版本的 ComboBox
采用了元素行为中的 ViewLink 功能,该功能允许您将文档片段封装在 HTML 组件中。ViewLink 元素行为(也称为主元素)也维护自己的文档树。这意味着我们现在可以使用 HTML 创建元素和样式表属性,并通过 HTC 文件中的脚本来操作这些对象,而无需担心名称冲突。
例如,ComboBox
定义了一个 ID 为 "textField
" 的文本 input
字段。如果使用传统的元素行为,我们必须使用 document.createElement
方法来定义元素,然后使用 element.appendChild
方法将其添加到主文档树中。然而,如果我们使用 View Link 功能,我们只需使用 HTML 来创建和定义我们的元素。此外,如果我们想在页面上使用多个 ComboBox
,或者使用另一个定义了具有相同 ID 的完全不同元素的行为,该怎么办?我们会遇到名称冲突。
<PUBLIC:COMPONENT tagName="COMBOBOX">
...
<STYLE TYPE="text/css">
.clsTextField {border:none;margin-right:1px;margin-left:1px;}
.clsTextFieldCell {background-color:white;border:ridge 1px buttonface;}
.clsTextFieldCell_hover {background-color:white;border:solid 1px navy;}
...
</STYLE>
<body>
<table unselectable="on" ID="tblCombobox"
cellspacing="0" cellpadding="0" border="0">
<tr>
<td unselectable="on" class="clsTextFieldCell"
id="textFieldCell">
<input class="clsTextField" id="textField"
type="text" NAME="textField">
</td>
<td id="dropDownArrowCell" class="clsDropDownCell">
<img width=5 height=3
id="imgArrow" src="images/down_arrow.gif" vspace="2"
hspace="3"/></td>
</tr>
</table>
</body>
</PUBLIC:COMPONENT>
您还会注意到我在 HTC 文件中定义了 CSS 样式类。在 mouseover
、mouseout
和 click
事件处理程序中会交换 className
属性。因此,如果您想修改外观和感觉,就必须在这里进行。
![]() |
主元素的尺寸由其内容片段定义。动态创建的元素,无论是否绝对定位,都会影响并受这些尺寸的限制。对于 ComboBox
,我们创建了一个下拉列表,其中包含一个 HTML 表格,该表格代表 ListItems
集合。每个表格单元格都会捕获窗口事件,例如 mouseout
、mouseover
和 click
。当添加到主元素的文档树时,新的下拉列表的尺寸会比初始尺寸大很多。正如您在图 1 中看到的,ComboBox
位于 HTML 表格内,单击时,下拉列表会扩展表格单元格的高度。下拉列表的右边缘也会被裁剪,因为没有显示滚动条。
正如您所见,这样做行不通。我们需要将新创建的表格附加到主文档树中。这样做可以让我们绝对定位,而不会改变主元素的尺寸。这还引出了另一个有趣的问题,textField
包含在主元素中。当主文档的表单提交时,textField
数据不会在请求中发送。如果您遍历 Forms 集合,将找不到 textField
。因此,为了使 ComboBox
参与回发,我们必须创建一个隐藏字段并将其附加到主元素的父 form
。
最后,我想提一下 options
集合。由于 ComboBox
的下拉列表本质上是一个 HTML 表格,并且所有的状态逻辑都封装在 ComboBox
本身中,所以我们实际上不需要创建传统的 HTML Select
元素来进行渲染。尽管 ComboBox
是为在服务器环境中使用而编写的,但在某些情况下,您可能希望从客户端动态添加或删除列表项。因此,我们必须将该集合公开为一个公共属性。我们可以选择创建自己的集合类并引入全新的 API,或者我们可以创建一个内存中的 select
元素,并将其 options
集合映射到公共属性。我选择了后者。由于 select
元素存在于内存中,并未在浏览器中渲染,因此我公开了 repaint()
方法,该方法将显式重绘下拉列表。添加或删除客户端项后,需要调用此方法。
已知问题
- 内联对齐 -
ComboBox
与相邻文本的对齐不正确。将控件放在表格单元格内以解决此问题。