动态填充 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
我有一个允许用户按姓名搜索客户的程序。到目前为止我这样做的方法(下面的代码)是让用户开始在文本框 (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