Home » excel » excel – How to use a Sub to populate values of a (dynamic) array

excel – How to use a Sub to populate values of a (dynamic) array

Posted by: admin May 14, 2020 Leave a comment

Questions:

I want to read-in the values of a column in an excel workbook (starting at cell(3, 1)) using a Sub which will then deliver the array to the main function, but the values obtained within the sub aren’t returning to the array in the main function.

I currently have values in Cells(3, 1) and (4, 1) and I know the Sub works because I put in a message box within the Sub and it reads both values.

I’ve tried turning the Sub into a function, changing the name of the Sub parameter to same name as the array in the main function (tr_des), and a lot of stuff like that.

Option Explicit
Private Sub cmd_openform_Click() '"Main" function
    Dim tr_des() As String
        Call getDescriptions(tr_des)
    uf_TestSelector.Show vbModal    'shows properly
    MsgBox tr_des(1)    'shows empty MsgBox
End Sub
Sub getDescriptions(ByRef des_array() As String)
    Dim descrip As String, size As Integer
    Dim i As Integer
    i = 0
    size = 1
    ReDim des_array(size)
    Do While Cells(i + 3, 1).Value <> ""
        des_array(i) = Cells(i + 3, 1).Value
        MsgBox des_array(i) 'opens MsgBox with correct value both times
        size = size + 1
        ReDim des_array(size)
        i = i + 1
    Loop
End Sub

I expected MsgBox tr_des(1) to return a value from the column from the excel worksheet but it always returns an empty MsgBox

How to&Answers:

You need to use ReDim Preserve.

If you do MsgBox des_array(i) after your ReDim, you’ll see the values are gone 🙂

The ReDim (without Preserve) re-allocates the array to the specified dimensions. Using ReDim Preserve is how you increase the size of the array without wiping out its content.

If you use the Preserve keyword, you can resize only the last array dimension and you can’t change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array.

Answer:

@DavidZemens introduced ReDim Preserve as one approach to solve your issue. I recommend a different programming approach that avoids the expensive VBA Preserve action (performance hit can be noticeable on large arrays, but may not be important in your case).

This approach uses a functional paradigm where a new array is calculated. The following is a simple re-write based on your code.

Option Explicit
Private Sub cmd_openform_Click() '"Main" function
Dim tr_des As Variant
    tr_des = getDescriptions
    uf_TestSelector.Show vbModal    'shows properly
    MsgBox tr_des(1)    
End Sub

Function getDescriptions() as Variant
Dim tValidRange as Range
Dim i As Integer
    Set tValidRange = Nothing ' Not really required but nice to be explicit
    i = 0
    Do While Cells(i + 3, 1).Value <> ""
        If tValidRange is Nothing Then
            Set tValidRange  = Cells(i + 3, 1)
        Else
            Set tValidRange  = Union(tValidRange,Cells(i + 3, 1))
            'Set tValidRange  = tValidRange.Resize(tValidRange.Rows.COunt + 1,1) ' Alternate approach
        End If
        i = i + 1
    Loop
    getDescriptions = tValidRange.Value ' Places values into an array.
End Sub

Of course, the new way of thinking leads to further refinement of the code.

Function getDescriptions() as Variant
Dim tValidRange as Range
Dim tRangeToCheck as Range
Dim i As Integer
    Set tValidRange = Nothing ' Not really required but nice to be explicit
    Set tRangeToCheck = Cells(3,1) 'This really should be fully qualified but ...
        ' ... you have not provided enough information for an example.

    Do While tRangeToCheck.Value <> ""
        If tValidRange is Nothing Then
            Set tValidRange  = tRangeToCheck
        Else
            Set tValidRange  = tValidRange.Resize(tValidRange.Rows.Count + 1,1) ' expand range down by one row.
        End If
        Set tRangeToCheck = tRangeToCheck.Offset(1,0) ' move down one row
    Loop
    getDescriptions = tValidRange.Value ' Places values into an array.
End Sub