动态填充 ListBox 的有效方法 vb.net

Efficient way to dynamically populate ListBox vb.net

我有一个允许用户按姓名搜索客户的程序。到目前为止我这样做的方法(下面的代码)是让用户开始在文本框 (tbCust) 中键入客户名称,代码在 TextChanged 事件上触发并根据用户键入的内容重新填充列表框。我认为这里的想法很明显并且很常用。

这在我的电脑上没有最小的延迟,但在一些其他用户的电脑上,这些电脑是更基本的机器,更新之间有 100 毫秒到 300 毫秒的延迟,这使得用户体验非常糟糕。

如果我在这里错了,请纠正我,但我觉得这个功能应该很容易实现,几乎任何一台计算机都不会出现任何滞后现象。

我认为还有更多 correct/efficient 的方法可以做到这一点,我只是不够聪明,无法自己想出(输入,你们所有人!)

请阐明可能更 'appropriate' 的实现更好性能的方法。我认为我的问题在于每次例程运行时(每次用户键入字母时)查询数据库,但我不确定在处理实时数据的同时还能如何做。

非常非常感谢!

我的计算机上可接受的性能视频:Youtube Video #1

用户计算机上不可接受的性能视频:YouTube Video #2

用户计算机规格:

    Private Sub tbCust_TextChanged(sender As Object, e As EventArgs) Handles tbCust.TextChanged
    'This populates the Customer Selection list box with customers whose names start with the
    'string of letters in the customer name text box.


    If tbCust.TextLength > 0 Then
        lbCustSelect.Visible = True
        Dim SQL As String
        SQL = "SELECT C_CUSTOMER as ID, C_SHIPNAME as Name FROM CUSTOMER WHERE LEFT(C_SHIPNAME," & tbCust.TextLength & ") ='" & tbCust.Text & "'"
        'Query Database
        AeroDBcon.RunQuery(SQL)

        'Fill DataTable with Query results
        dtCustomers = AeroDBcon.DBds.Tables(0)

        'Tie DataTable to ListBox
        lbCustSelect.DataSource = dtCustomers
        lbCustSelect.DisplayMember = "Name"
        lbCustSelect.ValueMember = "ID"

        'If there are no results, hide the ListBox
        If dtCustomers.Rows.Count = 0 Then
            lbCustSelect.Visible = False
        End If
    Else
        'if there is no text in the customer name text box, hide the listbox
        lbCustSelect.Visible = False
    End If
End Sub

在 SQL 中过滤通常比在客户端过滤更快。但是由于 table CUSTOMER 可能没有那么大,并且查询数据库似乎存在开销问题,所以让我们一次查询它,并在客户端进行过滤。

我喜欢强类型。即使您不使用 ORM,我们仍然可以创建一个 class 来保存您的结果:

Private Class Customer
    Public Property ID As String
    Public Property Name As String
End Class

如果我们持有所有客户的集合,

Private customers As IEnumerable(Of Customer)

就是这么过滤的

Dim filteredCustomers = customers.Where(Function(c) c.Name.StartsWith(filterString)).ToList()

此外,我不会运行按键查询。我也不会 运行 它在 UI 线程上(UI 事件处理程序 运行 在 UI 上,这将导致你的 UI 冻结而查询 运行s)。 运行 自上次按键后经过一定时间后的查询,运行 它关闭 UI。 System.Threading.Timer 非常适合这个。

Private ReadOnly queryTimer As New System.Threading.Timer(AddressOf executeQuery, Nothing, -1, -1)
Private ReadOnly keyPressDelay As Integer = 100
Private customers As IEnumerable(Of Customer)
Private filterString As String = ""

Private Sub tbCust_TextChanged(sender As Object, e As EventArgs) Handles tbCust.TextChanged
    filterString = tbCust.Text
    lbCustSelect.Visible = filterString.Length > 0
    If filterString.Length > 0 Then queryTimer.Change(keyPressDelay, -1)
End Sub

Private Sub executeQuery(state As Object)
    ' this could alternately be run in Form_Load
    If customers Is Nothing Then
        Dim sql = "SELECT C_CUSTOMER as ID, C_SHIPNAME as Name FROM CUSTOMER"
        AeroDBCon.RunQuery(sql)
        customers =
            AeroDBCon.DBds.Tables(0).
            AsEnumerable().Select(Function(dr) New Customer With {.ID = dr("ID").ToString(), .Name = dr("Name").ToString()})
    End If

    Dim filteredCustomers = customers.Where(Function(c) c.Name.StartsWith(filterString)).ToList()
    ' Dim filteredCustomers = customers.Where(Function(c) c.Name.Contains(filterString)).ToList()

    ' update the control on the UI thread
    lbCustSelect.Invoke(
        Sub()
            lbCustSelect.DataSource = Nothing
            lbCustSelect.DisplayMember = "Name"
            lbCustSelect.ValueMember = "ID"
            lbCustSelect.DataSource = filteredCustomers
        End Sub)
End Sub

您还应该在处理表单时处理计时器。将您的 Dispose 方法修改为此

Protected Overrides Sub Dispose(disposing As Boolean)
    Try
        If disposing Then
            components?.Dispose()
            queryTimer?.Dispose()
        End If
    Finally
        MyBase.Dispose(disposing)
    End Try
End Sub