Эффективный способ динамического заполнения ListBox vb.net - PullRequest
0 голосов
/ 03 октября 2019

У меня есть программа, которая позволяет пользователю искать клиента по имени. До сих пор я делал это (код ниже): пользователь начинает вводить имя клиента в TextBox (tbCust), код запускает событие TextChanged и заполняет ListBox в зависимости от того, что набрал пользователь. Я думаю, что идея здесь очевидна и широко используется.

Это работает без минимальной задержки на моем компьютере, но на компьютерах некоторых других пользователей, которые являются машинами базового уровня, между обновлениями существует задержка от 100 мс до 300 мс, что делаетдля довольно дрянного пользовательского опыта.

Поправьте меня, если я ошибаюсь, но я чувствую, что эта функциональность должна быть легко достижимой без какой-либо ощутимой задержки практически для любого компьютера.

Я предполагаю, что есть более правильный / эффективный способ сделать это, что я просто недостаточно умен, чтобы придумывать самостоятельно (войдите, все вы!)

Пожалуйста, отбросьтенекоторый свет на, возможно, более «подходящий» способ сделать это, что приведет к гораздо лучшей производительности. Я предполагаю, что моя проблема заключается в запросах к базе данных при каждом запуске подпрограммы (каждый раз, когда пользователь вводит букву), но я не уверен, как еще это сделать, продолжая работать с живыми данными.

Заранее большое спасибо!

Видео приемлемого исполнения на моем компьютере: Youtube Video # 1

Видео неприемлемого исполнения на компьютере пользователя: YouTube Video # 2

Характеристики компьютера пользователя: User Computer Specs

    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

1 Ответ

0 голосов
/ 03 октября 2019

Фильтрация в SQL обычно быстрее, чем фильтрация на стороне клиента. Но так как таблица CUSTOMER, вероятно, не так велика, и, похоже, возникает проблема с запросами к базе данных, давайте сразу запросим ее и отфильтруем на стороне клиента.

Мне нравится строгая типизация. Даже если вы не используете ORM, мы все равно можем создать класс для хранения ваших результатов:

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()

Кроме того, я бы не стал выполнять запрос при нажатии клавиш. Я также не буду запускать его в потоке пользовательского интерфейса (обработчики событий пользовательского интерфейса запускаются в пользовательском интерфейсе, и это приведет к зависанию вашего пользовательского интерфейса во время выполнения запроса). Запустите запрос через определенное время с момента последнего нажатия клавиши и запустите его из пользовательского интерфейса. A 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

Вы также должны утилизировать таймер при удалении формы. Измените свой метод утилизации на этот

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
...