在 ASP.NET 中创建多列组合框






4.62/5 (16投票s)
2005 年 9 月 5 日
7分钟阅读

315633

6613
在 ASP.NET 中创建一个多列组合框。
引言
之前,我遇到一个需求,需要提供一个功能,类似于传统的组合框(在 Windows 窗体中找到)和一个多列组合框。我在网上搜索了很多,但都没有找到适合我需求的。于是,我决定从头开始编写自己的控件,这就是我称之为多列组合框的东西。它是一个组合框,但有多列和一个文本框。您可以通过在文本框中键入关键列值来搜索特定行。
下面的截图显示了多列组合框的工作方式。第一列是用于搜索的关键列。在这种情况下,它是项目代码。当我输入 9 时,文本框会显示所有以数字 9 开头的项目代码,当我将鼠标移到任何一项上时,例如,移到一个项目代码为 93 的条目上,文本框就会反映当前的 item code。搜索智能提示会在您键入搜索文本框中的数据时自动打开。您可以通过点击搜索文本框右侧的按钮来显式打开搜索智能提示。
当搜索文本框为空且您显式单击按钮时,将显示所有项目,如下面的截图所示。主要目标是让用户能够看到数据。
需要注意的事项!使用时
使用多列组合框时,请移除位于搜索智能提示高度内的所有控件的 z-index。当 DropdownList
控件位于搜索智能提示的高度内时,也需要特别注意。DropDownList
始终显示在最前面,无论您设置什么 z-index。DropDownList
在 Windows 中的实现方式就是如此。在提供的示例中,我模拟了相同的操作。我在多列组合框下方使用了两个下拉列表,并通过多列组合框的 ControlsToHide
属性将它们隐藏。
<ControlsToHide>
<cc1:HiddenControl ControlId="DropDownList1"></cc1:HiddenControl>
<cc1:HiddenControl ControlId="DropDownList2"></cc1:HiddenControl>
</ControlsToHide>
得益于此属性,当显示多列组合框时,DropDownList1
和 DropDownList2
都将不可见。
您可以使用我在设计模式下提供的集合编辑器来设置此属性。
只有当下拉列表位于高度内时,才需要执行此步骤。(文本框和其他控件都可以。尽管我提供了一个通用模型,但如果任何控件干扰您,可以通过提及它的 ID 来将其排除在外!)这不是一个 bug,而是 DropdownList
控件的实现方式。我已经在我的控件中修复了这个 bug。您可以看到许多网站都有这个常见的 bug。如果您不执行此步骤,您将看到如下输出
属性
Columns
要在多列组合框列表中显示的列。请记住,关键列应该是多列组合框中的第一列,否则多列组合框会引发 JavaScript 错误。您可以通过操作我提供的 JavaScript 源代码来更改此行为。此外,只允许有一列关键列。否则会抛出“只允许一列关键列”的异常。
<Columns> <cc1:Column HeaderText="Item Code" KeyColumn="True" DataField="PKID" ColumnWidth="10%"></cc1:Column> <cc1:Column HeaderText="Item Name" KeyColumn="False" DataField="Name" ColumnWidth="80%"></cc1:Column> <cc1:Column HeaderText="UnitCost" KeyColumn="False" DataField="UnitCost" ColumnWidth="10%"></cc1:Column> </Columns>
您可以从
Columns
属性集合编辑器中设置此属性。AutoPostBack
此属性指示单击多列组合框中的项目时是否触发回发。如果为 true,则会触发
RowSelectionChanged
事件,该事件在 aspx 页面中捕获,如下所示在我的示例中,我通过编写如下代码捕获了自动回发事件
txtName.Text = args.Cells[1].ToString();//item name column
您可以按索引访问单元格值,因为
Cells
属性使用了ArrayList
类。例如args.Cells[0].ToString();//item code args.Cells[1].ToString();//item name args.Cells[2].ToString();//unit cost
ItemMouseOverColor
鼠标移到项目上时使用的颜色。
GridLinesColor
搜索网格的网格线颜色。
DownArrowButtonHeight
向下箭头按钮的高度。
DownArrowButtonWidth
向下箭头按钮的宽度。
ValidatorErrorMessage
如果搜索文本框中未输入任何数据,则显示的错误消息。
ValidatorText
验证器的文本。
ValidatorDisplayStyle
验证器在页面上的渲染方式。
ValidatorEnabled
如果要对搜索文本框使用必需字段验证,则通过将其设置为
true
来启用它,否则设置为false
。ValidatorTooltip
要在验证器中显示的工具提示。
ComboBoxListHeight
智能提示的高度。
ComboBoxListWidth
智能提示的宽度。
TextBoxWidth
搜索数据文本框的宽度。
ValidatorCSS
必需验证器的 CSS 类。
HeaderCSS
网格标题的 CSS 类。
ItemsCSS
网格项目的 CSS 类。
DownArrowButtonCSS
向下箭头按钮的 CSS 类。
TextBoxCSS
搜索数据文本框的 CSS 类。
HorizontalScrolling
是否使用水平滚动。
VerticalScrolling
是否使用垂直滚动。
ControlsToHide
所有可能出现在多列组合框之上的控件的控件 ID(例如,
DropDownList
控件)。您可以使用此属性指定所有这些控件的 ID。<ControlsToHide> <cc1:HiddenControl ControlId="DropDownList1"></cc1:HiddenControl> <cc1:HiddenControl ControlId="DropDownList2"></cc1:HiddenControl> </ControlsToHide>
数据验证支持
多列组合框内部使用 RequiredFieldValidator
来验证数据。您可以通过设置验证器属性来启用或禁用验证。默认情况下,验证是禁用的。
当您单击“Post”按钮时,您会看到验证正在生效,如下所示
多列组合框的工作原理
多列组合框是一个复合服务器控件,它在其 CreateChildControls()
方法中创建一个子服务器控件。此方法通知服务器控件创建其中包含的任何子控件。在实现复合控件时,您必须执行两项关键任务:
- 重写
CreateChildControl
方法以实例化子控件、初始化它们并将它们添加到父控件的控件层次结构中。 - 实现
INamingContainer
接口,该接口在您的控件下创建一个新的命名范围。
Public Class MultiColumnComboBox _
Inherits System.Web.UI.WebControls.WebControl _
Implements INamingContainer
现在我们来讨论什么是命名范围?这个概念类似于 C++ 和 C# 中的命名空间概念。例如,在 .NET Framework 中,DataSet
类位于 System.Data
命名空间中。如果不引用 System.Data
命名空间,您将无法直接访问它。同样的规则也适用于多列组合框和所有复合服务器控件。例如,当多列组合框搜索文本框在浏览器中呈现时,它会生成一个客户端 ID,如 MultiColumnComboBox1:txtData
。这个 ID 表明 txtData
位于 MultiColumnComboBox1
的范围内,而 MultiColumnComboBox1
是父服务器控件的 ID。
您无需生成此 ID,因为 INamingContainer
会自动为您完成此操作。实现此接口时,它会为服务器控件创建一个新的命名范围,并为所有子控件生成一个唯一的 ID。因此,如果您在 aspx 页面中使用多列组合框的两个实例,您会遇到类似以下情况
MultiColumnComboBox1:txtData
MultiColumnComboBox2:txtData
尝试移除 INamingContainer
接口并观察其效果。
现在轮到 CreateChildControl()
方法了。在其中,我创建了所有子控件。Controls.Clear()
语句确保不会将子控件的多个副本添加到服务器控件的 Controls
集合中。其余部分非常直观。
Controls.Clear()
''----Data Text box
With txtData
.ID = "txtData"
If Not Me.ViewState("TextBoxWidth") Is Nothing Then
.Width = Me.ViewState("TextBoxWidth")
End If
If Not Me.ViewState("TextBoxCSS") Is Nothing Then
.CssClass = Me.ViewState("TextBoxCSS")
End If
End With
''---Down Arrow Button
btnDownArrow = New HtmlInputButton
With btnDownArrow
.ID = "btnDownArrow"
If Not Me.ViewState("DownArrowButtonWidth") Is Nothing Then
.Style("Width") = Me.ViewState("DownArrowButtonWidth").ToString()
End If
If Not Me.ViewState("DownArrowButtonHeight") Is Nothing Then
.Style("height") = Me.ViewState("DownArrowButtonHeight").ToString()
End If
If Not Me.ViewState("DownArrowButtonCSS") Is Nothing Then
.Attributes("class") = Me.ViewState("DownArrowButtonCSS")
End If
End With
''----Required Field Validator
If Not Me.ViewState("ValidatorEnabled") Is Nothing AndAlso _
Me.ViewState("ValidatorEnabled") = True Then
rfData = New RequiredFieldValidator
With rfData
.ControlToValidate = txtData.ID
If Me.ViewState("ValidatorErrorMessage") Is Nothing Then
.ErrorMessage = "Please Enter Data"
Else
.ErrorMessage = Me.ViewState("ValidatorErrorMessage")
End If
If Me.ViewState("ValidatorText") Is Nothing Then
.Text = "*"
Else
.Text = Me.ViewState("ValidatorText")
End If
If Not Me.ViewState("ValidatorDisplayStyle") Is Nothing Then
.Display = Me.ViewState("ValidatorDisplayStyle")
Else
.Display = ValidatorDisplay.None
End If
If Not Me.ViewState("ValidatorCSS") Is Nothing Then
.CssClass = Me.ViewState("ValidatorCSS")
End If
End With
End If
''----Data Grid-----
dgSearch = New DataGrid
With dgSearch
.ID = "dgSearch"
.BorderWidth = New Unit(1)
.GridLines = GridLines.Both
.Width = New Unit("100%")
If Not Me.ViewState("HeaderCSS") Is Nothing Then
.HeaderStyle.CssClass = Me.ViewState("HeaderCSS")
End If
If Not Me.ViewState("ItemsCSS") Is Nothing Then
.ItemStyle.CssClass = Me.ViewState("ItemsCSS")
End If
If Not Me.ViewState("GridLinesColor") Is Nothing Then
.BorderColor = Me.ViewState("GridLinesColor")
Else
.BorderColor = Color.Black
End If
End With
代码的后半部分对于大多数开发人员来说是一个棘手的挑战。现在让我们来解决它。我以编程方式为 ASP.NET DataGrid
创建动态模板列。通常我们在设计模式下设置模板列,但在本例中,我们为 ASP.NET DataGrid
创建动态模板列。在以编程方式创建时,您应该实现 ITemplate
接口,如下所示:
Friend Class SearchGridTemplateColumn _
Implements ITemplate
在实例化方法中,我将 LinkButton 添加到控件层次结构中。接下来是将 LinkButton 与数据源绑定。为此,我这样做:
AddHandler lk.DataBinding, AddressOf DataBind
我在这里做的是设置 LinkButton
的 DataBinding
事件的处理程序。当您编写 dgSearch.DataBind()
并 subsequently 调用所有子控件的 DataBinding
事件时,就会调用此方法。
Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) _
Implements System.Web.UI.ITemplate.InstantiateIn
Dim lk As LinkButton
Select Case templateType
Case ListItemType.Header
Case ListItemType.Footer
Case ListItemType.Item
lk = New LinkButton
AddHandler lk.DataBinding, AddressOf DataBind
lk.ID = ctrlId
'lkSelect.CommandName = "Select"
container.Controls.Add(lk)
End Select
End Sub
现在 comes a more nasty part of the code. Take it another way that I am doing dynamic coding for statements like <%# DataBinder.Eval(Container.DataItem,”MyColumn”)%>
. Whenever data is displayed (for example, in a DataGrid
control), only one version of each row can be displayed. The displayed row is a DataRowView
. In the source code below, I am getting the NamingContainer
of the LinkButton
with the statement CType(lk.NamingContainer, DataGridItem)
which is an instance of DataGridItem
. I am rendering the LinkButton
in each row of the DataGrid
and in the rendering process I need to know about every LinkButton
. The variable dgi
(dgi
is present in the source code below) gives me a single item of the DataGrid
and with it I refer an individual item of the data source of DataGrid
. The sub statement Row.Item(Me.columnName)
actually refers to the value of that row which is rendered in each column of the DataGrid
.
Protected Sub DataBind(ByVal sender As Object, ByVal e As EventArgs)
Dim lk As LinkButton
lk = CType(sender, LinkButton)
Dim dgi As DataGridItem
dgi = CType(lk.NamingContainer, DataGridItem)
lk.Text = CType(CType(dgi.DataItem, _
DataRowView).Row.Item(Me.columnName), String)
End Sub
现在我们回到 CreateChildControls()
方法。每当以编程方式在 DataGrid
中添加模板列时,请始终创建 TemplateColumn
类的实例。
Dim enmDataSource As IEnumerator = dtSource.GetList().GetEnumerator()
Dim enmColumns As IEnumerator = Columns.GetEnumerator
Dim tc As TemplateColumn
Dim bc As BoundColumn
Dim col As Column
Dim keyColCount As Integer = 0
While enmColumns.MoveNext
If keyColCount > 1 Then
Throw New Exception("Only one key column is allowd")
End If
col = CType(enmColumns.Current, Column)
Dim lkColId As String
If Not col.KeyColumn Then
'bc = New BoundColumn
'bc.DataField = col.DataField
'bc.HeaderText = col.HeaderText
'bc.HeaderStyle.Wrap = False
'If col.ColumnWidth.ToString().Length > 0 Then
' bc.ItemStyle.Width = col.ColumnWidth
'End If
'dgSearch.Columns.Add(bc)
lkColId = "lk" & col.DataField
tc = New TemplateColumn
tc.ItemTemplate = _
New SearchGridTemplateColumn(ListItemType.Item, _
col.DataField, lkColId)
tc.HeaderText = col.HeaderText
tc.HeaderStyle.Wrap = False
If col.ColumnWidth.ToString().Length > 0 Then
tc.ItemStyle.Width = col.ColumnWidth
End If
dgSearch.Columns.Add(tc)
Else
tc = New TemplateColumn
tc.ItemTemplate = _
New SearchGridTemplateColumn(ListItemType.Item, _
col.DataField)
tc.HeaderText = col.HeaderText
tc.HeaderStyle.Wrap = False
If col.ColumnWidth.ToString().Length > 0 Then
tc.ItemStyle.Width = col.ColumnWidth
End If
dgSearch.Columns.Add(tc)
keyColCount += 1
End If
End While
dgSearch.AutoGenerateColumns = False
dgSearch.DataSource = dtSource
dgSearch.DataBind()
End If