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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (16投票s)

2005 年 9 月 5 日

7分钟阅读

viewsIcon

315633

downloadIcon

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>

得益于此属性,当显示多列组合框时,DropDownList1DropDownList2 都将不可见。

您可以使用我在设计模式下提供的集合编辑器来设置此属性。

只有当下拉列表位于高度内时,才需要执行此步骤。(文本框和其他控件都可以。尽管我提供了一个通用模型,但如果任何控件干扰您,可以通过提及它的 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

我在这里做的是设置 LinkButtonDataBinding 事件的处理程序。当您编写 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
© . All rights reserved.