2

I have this code which basically filters the values in listbox as the value changes in textbox on userform in excel

Private Sub TextBox1_Change()

Dim sht As Worksheet
Dim rng1 As Range
Set sht = Sheet5
Set rng1 = sht.Range("F2:F" & sht.Range("F" & sht.Rows.Count).End(xlUp).Row)

ListBox2.ColumnCount = 7

'=====
Dim i As Long
Dim arrList As Variant

Me.ListBox2.Clear
If sht.Range("F" & sht.Rows.Count).End(xlUp).Row > 1 Then
    arrList = sht.Range("F2:L" & sht.Range("F" & sht.Rows.Count).End(xlUp).Row).Value2
    For i = LBound(arrList) To UBound(arrList)
        If InStr(1, arrList(i, 1), Trim(Me.TextBox1.Value), vbTextCompare) Then
            liste = ListBox2.ListCount
            Me.ListBox2.AddItem
            Me.ListBox2.List(liste, 0) = arrList(i, 1)
            Me.ListBox2.List(liste, 1) = arrList(i, 2)
            Me.ListBox2.List(liste, 2) = arrList(i, 3)
            Me.ListBox2.List(liste, 3) = arrList(i, 4)
            Me.ListBox2.List(liste, 4) = arrList(i, 5)
            Me.ListBox2.List(liste, 5) = arrList(i, 6)
            Me.ListBox2.List(liste, 6) = arrList(i, 7)

        End If
    Next i
End If

If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True

End Sub

It works perfectly except when I change the value from something to nothing i.e. blank it takes about 4 to 5 seconds to finish populating about 8k rows * 7 columns of data from the sheet in the listbox, which is not desirable. Is there any way we can speed things up here?

Community
  • 1
  • 1
Rohan
  • 319
  • 1
  • 5
  • 18
  • Hi, how do you change the value to nothing? I can't find that line in your code. –  Oct 27 '17 at 21:05
  • @DavidG.manually. This is a textbox change event sub. I enter some value and then delete it using backspace. – Rohan Oct 27 '17 at 21:14
  • by putting `Trim(Me.TextBox1.Value)` in a variable string, you wont have to calculate it each loop – Patrick Lepelletier Oct 28 '17 at 08:41
  • @Rohan, I added a high speed method to populate your listbox with your complete data by one code line and added ideas for further improvement. – T.M. Oct 29 '17 at 19:21

3 Answers3

4

After put datas to a new array, set listbox by new array.

Private Sub TextBox1_Change()

Dim sht As Worksheet
Dim rng1 As Range
Dim vR() As Variant

Set sht = Sheet5
Set rng1 = sht.Range("F2:F" & sht.Range("F" & sht.Rows.Count).End(xlUp).Row)

ListBox2.ColumnCount = 7

'=====
Dim i As Long
Dim arrList As Variant

Me.ListBox2.Clear
If sht.Range("F" & sht.Rows.Count).End(xlUp).Row > 1 Then
    arrList = sht.Range("F2:L" & sht.Range("F" & sht.Rows.Count).End(xlUp).Row).Value2
    For i = LBound(arrList) To UBound(arrList)
        If InStr(1, arrList(i, 1), Trim(Me.TextBox1.Value), vbTextCompare) Then
            n = n + 1
            ReDim Preserve vR(1 To 7, 1 To n)
            For j = 1 To 7
                vR(j, n) = arrList(i, j)
            next j
        End If
    Next
     Me.ListBox2.List = WorksheetFunction.Transpose(vR)
End If

If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True

End Sub
Dy.Lee
  • 7,527
  • 1
  • 12
  • 14
  • `next j` , not `nex j` – Patrick Lepelletier Oct 28 '17 at 08:44
  • instead of `redim preserve` at each loop, you can redim it at the begin of the loop, same size as arrList (max size), and after `Next i`, you redim it again, `1 to n` – Patrick Lepelletier Oct 28 '17 at 08:53
  • @PatrickLepelletier, Thans for comment. I corrected the typos. – Dy.Lee Oct 28 '17 at 09:28
  • @PatrickLepelletier, arrList size and vR() size is not same. So I used redim preserve. – Dy.Lee Oct 28 '17 at 09:32
  • @Dy.Lee, friendly suggestion: equate `sht.Range("F" & sht.Rows.Count).End(xlUp).Row` to a variable. ... it is in the code three times and using a variable will de-clutter the code a lot – jsotola Oct 29 '17 at 05:19
  • @Dy.Lee, just to show a method to avoid permanent redimensioning via `ReDim Preserve vR(1 To 7, 1 To n)`, one could use the `Filter` function to obtain the FINAL number of findings already before looping through the array by: `n = UBound(Filter(Application.Transpose(Application.Index(arrList, , 1)), Trim(Me.TextBox1.Value), , vbTextCompare)) + 1`. Nevertheless, testing this showed no improvement of speed to your fast solution when searching a string with length >= 0. – T.M. Nov 04 '17 at 09:37
  • @Dy.Lee, your solution is fine, but not complete: a) if nothing is found you get run time error 5 when transposing, b) if only 1 item is found, then the listbox shows 7 rows instead of a one liner with 7 columns. - I highly appreciate your fast approach (+1) and show a workaround in a subsequent edit to my own answer. – T.M. Nov 04 '17 at 09:37
  • @T.M., Your suggestion will be solved by: if n = 1 then Me.ListBox2.List = vR elsif n> 1 then Me.ListBox2.List = WorksheetFunction.Transpose (vR) else end if. – Dy.Lee Nov 04 '17 at 13:43
  • @Dy.Lee, glad my hint was of some help to find a nearly perfect solution. It's worth mentioning with regard to other users, that both our approaches are not only fast, but overcome the usual **10 columns limitation** of the `AddItem`method in the OP when adding items to a listbox. – T.M. Nov 04 '17 at 16:51
  • @Dy.Lee, tested your above comment, but `if n = 1 then Me.ListBox2.List = vR ` **doesn't work**. Seems it's the only way to use my work around. – T.M. Nov 04 '17 at 17:08
  • 1
    @T.M., I was a bit inaccurate. if n = 1 then Me.ListBox2.column = vR – Dy.Lee Nov 05 '17 at 01:03
2

How to reduce needed time to nearly zero

The trick to speed up populating about 8k rows * 7 columns of data from the sheet in the listbox is not to use AddItemeach time, but to set a whole array to the listbox:

    Me.ListBox2.List = a

after checking if the search string s is empty by

    If Len(s) = 0 Then                                      

Code

Option Explicit

Private Sub TextBox1_Change()

Dim t       As Double     ' Timer
Dim oSht    As Worksheet
'=====
Dim liste   As Long
Dim i       As Long
Dim j       As Long
Dim n       As Long
Dim s       As String
Dim a       ' data field array, variant! (shorter for arrList)

t = Timer
Set oSht = ThisWorkbook.Worksheets("Test")          ' set worksheet fully qualified reference to memory

ListBox2.ColumnCount = 7                            ' dimension listbox columns

s = Me.TextBox1.Value                               ' get search string
Me.ListBox2.Clear                                   ' clear listbox
n = oSht.Range("F" & oSht.Rows.Count).End(xlUp).Row ' get last row number
If n > 1 Then                                       ' at least 1 line needed
  ' write range to one based 2dim data field array
    a = oSht.Range("F2:L" & n).Value2

    If Len(s) = 0 Then                              ' check if EMPTY string
    '   ====================================
    '   Trick: add complete items all in one
    '   ====================================
        Me.ListBox2.List = a                        ' avoids loop
        Debug.Print "Time needed: " & Format(Timer - t, "0.00 ") & " seconds." & vbNewLine & _
                    "Empty string """": all " & UBound(a) & " items refreshed."
    Else
    ' loop through ONE based 2dim array
      For i = LBound(a) To UBound(a)

        If InStr(1, a(i, 1), Trim(s), vbTextCompare) Then
           Me.ListBox2.AddItem                      ' add new listbox item
         ' enter 7 column values
           For j = 1 To 7                           ' ListBox2.List is ZERO based!!
               Me.ListBox2.List(Me.ListBox2.ListCount - 1, j - 1) = a(i, j)
           Next j
        End If

      Next i
      Debug.Print "Time needed: " & Format(Timer - t, "0.00 ") & " seconds." & vbNewLine & _
                  "Search string """ & s & """:" & Me.ListBox2.ListCount & " items found."

    End If
End If
If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True
End Sub

Note

My concern was to improve speed after empty string enters. So I concentrated on this part and left your further code nearly as it was, but did polish it up a little bit to make it more readable and used shorter names (e.g. ainstead of arrList). In order to control that, I added a Timer. BTW, I think you forgot some variable declarations.

Idea for further improvement of speed

If you want to speed up the normal string search, I'd suggest to use the following steps:

  • use advanced filtering into a temporary worksheet,
  • read the content into a new datafield array,
  • write it back to the listbox via the described method and
  • (delete the temporary worksheet afterwards).

Sure you'll find the right code to do so :-)

Additional hint

I recommend reading "Arrays and Ranges in VBA" by C.Pearson at http://www.cpearson.com/excel/ArraysAndRanges.aspx. For an example how to manipulate listboxes see also Excel VBA - avoid Error 1004 writing UF ListBox Array to Sheet

Good luck!

===================================================

Subsequent Edit (cf preceding comments as of 11/4-5)

This reedit combines not only the advantages of speeding up (A) empty string search (cf. my own answer above) with (B) Dy Lee's very fast and highly appreciated approach (search string not empty), but completes his solution by considering one liners and "zero" liners, too.

The recently suggested solution distinguishes between one liners and others

     '' ===========================
      '' B1 get one liners correctly
      '' ===========================
      '  If ii = 1 Then
      '     Me.ListBox2.Column = vR
      '' ===============================================
      '' B2 get others with exception of 'zero' findings
      '' ===============================================
      '  ElseIf ii > 1 Then
      '     Me.ListBox2.List = WorksheetFunction.Transpose(vR) ' not necessary, see below
      '  End If

but can be replaced by ONE code line only, as the ListBox.Column property retransposes the already transposed vR array in ANY case correctly to a 2dim array

         Me.ListBox2.Column = vR

whereas the ListBox.List property would do a double job in this case.

Additional hint:

It's worth mentioning, that populating listboxes via data field arrays helps to overcome the built-in **10 columns listbox limitation" when using the AddItem method.

Summarized Code

The following - slightly modified - code should summarize all points and help other users to comprehend all improvements made (thx @Dy.Lee):

Dy Lee's solution refined and commented

Option Explicit
Private Sub TextBox1_Change()
' Note:    based on Dy.Lee's approach including zero and one liners
' Changes: a) allows empty string search by one high speed code line
'          b) writes back one liners correctly via .Column property instead of .List property (cf. comment)
'          c) excludes zero findings to avoid error msg
' declare vars
  Dim t       As Double                          ' Timer
  Dim s       As String                          ' search string
  Dim oSht    As Worksheet                       ' work sheet
  Dim r       As Range
  '=====
  Dim a       As Variant                         ' one based 2-dim data field array
  Dim vR()    As Variant                         ' transposed array
  Dim i       As Long                            ' rows
  Dim j       As Long                            ' columns
  Dim ii      As Long                            ' count findings
  Dim jj      As Long                            ' count listbox columns (.ColumnCount)
  Dim n       As Long                            ' last row
  Dim nn      As Long                            ' findings via filter function
  t = Timer                                      ' stop watch
  s = Me.TextBox3                                ' get search string
  Set oSht = ThisWorkbook.Worksheets("Test")
' get last row number
  n = oSht.Range("F" & oSht.Rows.count).End(xlUp).Row
  if n = 1 then exit sub                 ' avoids later condition

  ListBox2.ColumnCount = 7                       ' (just for information)
  jj = ListBox2.ColumnCount
  ListBox2.Clear                                 ' clear listbox elements

' write range to one based 2dim data field array
  a = oSht.Range("F2:L" & n).Value2

' ========================
' A) EMPTY string findings                ' show all items
' ========================
If Len(s) = 0 Then                               ' check if EMPTY string
  ' ====================================
  ' Trick: add complete items all in one
  ' ====================================
    Me.ListBox2.List = a                         ' avoid loops, double speed
' ========================
' B) other actual findings
' ========================
Else                         ' 

   ' write results to redimmed and transposed array
     For i = LBound(a) To UBound(a)
         If InStr(1, a(i, 1), Trim(s), vbTextCompare) Then
                ii = ii + 1
                ReDim Preserve vR(1 To jj, 1 To ii)
                For j = 1 To jj
                    vR(j, ii) = a(i, j)
                Next j
         End If
      Next
    ' ==============================
    ' B1-B2) get any actual findings (retransposes both cases correctly to 2dim!)
    ' ==============================
      If ii >=1 then ListBox2.Column = vR ' exclude "zero" lines
End If

If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True

' time needed
  Debug.Print "Time needed: " & Format(Timer - t, "0.00 ") & " seconds." & _
                " - Search string """ & s & """: " & Me.ListBox2.ListCount & " items found."
End Sub
Community
  • 1
  • 1
T.M.
  • 9,436
  • 3
  • 33
  • 57
  • if n = 1 then Me.ListBox2.Column = vR – Dy.Lee Nov 05 '17 at 02:45
  • @Dy.Lee, I made a reedit to summarize all improvements. Rereading help after a longer time, I found out that using the listboxe's `.Column` property even spares to distinguish between one liners (`.Column`) and higher findings (`.List`) as the vR array is already transposed by ReDim and both methods transpose back correctly. So I learnt, too. Additional note: I used another variable Name `ii` instead of your `n`. – T.M. Nov 05 '17 at 10:01
0

use the rowsource property

Option Explicit

Private Sub TextBox1_Change()

    Dim sht As Worksheet
    Set sht = Sheet1

    Dim dataEnd as long
    dataEnd = sht.Range("F" & sht.Rows.Count).End(xlUp).Row

    Dim rng1 As Range
    Set rng1 = sht.Range("F2:F" & dataEnd)

    ListBox2.ColumnCount = 7
    ListBox2.ColumnWidths = "30 pt;30 pt;30 pt;30 pt;30 pt;30 pt;30 pt"
    '=====
    Dim i As Long
    Dim listData As Range

    ' Me.ListBox2.Clear
    If dataEnd > 1 Then
        Set listData = sht.Range("F2:L" & dataEnd)

        Me.ListBox2.RowSource = Sheet2.Name & "!" & listData.Address  ' this fills the listbox

    End If

    If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True

End Sub
jsotola
  • 2,238
  • 1
  • 10
  • 22
  • 1
    Where in the code does it check for partial match of the string entered in textbox? – Rohan Oct 28 '17 at 04:31
  • if you use it as range, than rename it as rangeList or something. ArrList is too confusing when someone else has to read your code. – Patrick Lepelletier Oct 28 '17 at 08:49
  • my apologies. i don't know what i was thinking. totally missed the partial match portion. the one posted by Dy.Lee looks very good. – jsotola Oct 29 '17 at 05:21