Search code examples
arraysexceluserformvba

Excel VBA - avoid Error 1004 writing UF ListBox Array to Sheet


Task

My intention is to write back UserForm Listbox data to a sheet range in the most efficient way using the data field method.

Basic Method (esp. using data ranges)

AFAIK the most efficient method in order to write data to a range is using arrays. If data originate from a range it's preferrable to use data field arrays, which by default are two dimensional (one based arrays) and allow to a) get data from sheet to array or b) write back the array to sheet with one code line:

Dim v   ' As Variant
Dim rng As Range
Set rng = ...

' a) range to array
      v = rng.Value

' b) array to range
     rng.Value = v

Differences when using the ListBox data array

ListBox data are stored in a 2dim, but zero based array, e.g. ListBox1.List

[Hint: Therefore it's possible to create items using a predefined array instead of using the commonly known Add method, BTW restricted to only 10 columns (indexed 0 to 9). ]

Despite this difference, it is possible to read in the ListBox data and write them back to sheet with the described basic method:

 ' aa) ListBox data to array
   v = Me.ListBox1.List

 ' bb) array to range
rng.Value = v

Furthermore, I remarked that by default the array columns count 10, so this won't correspond to the programmatically set ColumnCount. Therefore it's necessary to adapt the range checking the array dimensions, cf. debug protocol in the code example.

Problem and work around

Writing back the data field array to sheet in an one liner raises error 1004 (Application-defined or object-defined error) if there are items with a leading "==" string or comparable user inputs, as this can't be interpreted correctly by Excel.

My attempt to use

rng.Text  = v

instead of

rng.value = v

did fail, too, causing a 424 object needed error.

Question ==> Is it possible to correct the potential error items with leading "=" character without looping through all array items using VBA or API methods? Any Code example is welcome to replace my work around (step 3 in code)

My Code

My code consists of four steps 1) create a 2dim data field array (v) 2) set a target range (rng) 3) [my work around correcting each array item] 4) write data back to worksheet

Private Sub CommandButton1_Click()
' declare variables
  Dim ws As Worksheet
  Set ws = ThisWorkbook.Worksheets("Dump")

  Dim s     As String             ' range address string
  Dim sTest As String             ' item test string
  Dim v     As Variant            ' data field array
  Dim rng   As Range              ' (target) range
  Dim i     As Long               ' rows counter
  Dim j     As Long               ' columns counter
  Dim R     As Long               ' number of rows
  Dim C     As Integer            ' number of columns
  Dim lbxR  As Long               ' actual number of listbox items   in data field
  Dim lbxC  As Integer            ' actual number of listbox columns in data field
' Error Handler
  On Error GoTo OOPS

' get programmatically defined listbox dimension
  R = Me.ListBox1.ListCount
  C = Me.ListBox1.ColumnCount
' clear sheet lines A2:A{r}
  ws.Range(ws.Cells(2, 1), ws.Cells(R, C)).value = ""
' ===============================
' 1) create 2dim data field array
' -------------------------------
  v = Me.ListBox1.List

' -------------------------------
' 2) set target range (rng)
' -------------------------------
' get actual data field dimension
  lbxR = UBound(v, 1) - LBound(v, 1) + 1    ' first dimension
  lbxC = UBound(v, 2) - LBound(v, 2) + 1    ' second dimension
  s = ws.Range("A2").Resize(lbxR, lbxC).Address
' set correconding target range
  Set rng = ws.Range(s)                           ' target range

' create short protocol - columns by default differ from user defined ColumnCount property !
  Debug.Print String(80, "-")
  Debug.Print vbNewLine & "** ListBox1 Defs            Data Field Array Dimension **"
  Debug.Print "                            [Target Range " & s & "]"
  Debug.Print String(80, "-")
  Debug.Print "   ListCount  = " & ListBox1.ListCount, "rows = " & lbxR & " = ubound(v,1)-lbound(v,1)+1 = " & UBound(v, 1) & " - " & LBound(v, 1) & " + 1 "
  Debug.Print "   ColumnCount= " & ListBox1.ColumnCount, "cols = " & lbxC & " = ubound(v,2)-lbound(v,2)+1 = " & UBound(v, 2) & " - " & LBound(v, 2) & " + 1 "
  Debug.Print String(80, "-")

' ----------------------------------------------------------------
' 3) Work around - correct leading "=", if any occurences presumed
'    (avoid error 1004 - App-defined or object-defined error)
' ----------------------------------------------------------------
' ==> Is there an alternative way? 
' For i = 0 To R - 1            ' edited thx to D.Lee 
'   For j = 0 To C - 1
'      v(i, j) = IIf(Left(Me.ListBox1.List(i, j) & "", 1) = "=", " ", "") & _
'                          Me.ListBox1.List(i, j)
'   Next j
' Next i     
' -------------------------------
' 4) write data back to worksheet
' -------------------------------
  rng.value = v

Exit Sub

' =============
' Error Handler
' =============
OOPS:
  MsgBox "ERL=" & Erl & " |Error " & Err.Number & "|" & Err.Description & vbNewLine & _
         "s=" & s, vbExclamation

End Sub  

Tip I recommend reading "Arrays and Ranges in VBA" by C.Pearson

Addendum to OP from Sep 17 '17(alternative solution)

If you know for example that the first target column is text only, you can code the following statement before section [4] instead of searching for each list item starting with =:

rng.Columns(1).NumberFormat = "@"

Solution

  • Lbound(v,1) = 0 ubound(v,1) = r-1 so, it needs to modify i and j.

      For i = 0 To R - 1
          For j = 0 To C - 1
             v(i, j) = IIf(Left(Me.ListBox1.List(i, j) & "", 1) = "=", " ", "") & _
                                 Me.ListBox1.List(i, j)
          Next j
      Next i