2
\$\begingroup\$

I have written some code to extract a list of worksheets in the ActiveWorkbook, but only for sheets that have a numeric name.

This works, but I would like to know the Code Review community's thoughts on my methods used here.

Public Sub listNumberedSheets()
 Dim sheetNumberArray As Variant
 ReDim sheetNumberArray(0)
 Dim x As Long
 x = 0
 Sheets(1).Columns(1).ClearContents
 Dim sht As Worksheet
 For Each sht In ActiveWorkbook.Sheets
 If IsNumeric(sht.Name) Then
 ReDim Preserve sheetNumberArray(x)
 sheetNumberArray(x) = sht.Name
 x = x + 1
 End If
 Next
 Sheets(1).Cells(1, 1).Resize(x, 1).Value = Application.Transpose(sheetNumberArray)
End Sub
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Oct 13, 2017 at 21:15
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Sheets(1).Columns(1).ClearContents

It's not a big deal in this case since the macro is always meant to work with the active workbook, but generally you want to preface a "Sheets" reference with an explicit reference to the workbook.

It's also generally not a good idea to refer to sheets by number. Using sheet names tends to be easier to read/debug.

My recommendation would be to add some parameters to the macro so that you can explicitly specify both a) the workbook whose sheets' names you want to check, and b) the worksheet on which to print the results. You can make the parameters optional if you'd like, and set them to "ActiveWorkbook" and "ActiveWorkbook.Sheets(1)" if they're missing from the call.

Application.Transpose(sheetNumberArray)

Once again, this isn't a huge deal in this particular macro, but Application.Transpose has some significant limitations (it doesn't work with very large arrays) and should be avoided if possible. In your case, since you're eventually just writing the results of your routine to the worksheet, ideally you'd just make your array two-dimensional:

ReDim resultsArr(1 To wb.Sheets.Count, 1 To 1) As Double

But currently that won't work because you're using...

ReDim Preserve sheetNumberArray(x)

Which brings me to my final recommendation: ReDim Preserve has high performance costs, and should be avoided if possible (especially uses like this in which it's called repeatedly). In your case, it's pretty easy to avoid, as you should be able to see in my revised code below.

Sub exampleMacro()
 'Checks sheets in specified workbook, prints to specified worksheet
 Call listNumberedSheets(ThisWorkbook, ThisWorkbook.Sheets("Results"))
 'Checks sheets in active wb, prints to Sheets(1) of active wb
 Call listNumberedSheets
End Sub
Sub listNumberedSheets(Optional wbToCheck As Workbook = Nothing, Optional resultsWS As Worksheet = Nothing)
 If wbToCheck Is Nothing Then
 Set wbToCheck = ActiveWorkbook
 End If
 If resultsWS Is Nothing Then
 Set resultsWS = ActiveWorkbook.Sheets(1)
 End If
 ReDim resultsArr(1 To wbToCheck.Sheets.Count, 1 To 1) As Double
 Dim i As Long
 Dim j As Long
 j = 1
 For i = 1 To wbToCheck.Sheets.Count
 Dim shtName As String
 shtName = wbToCheck.Sheets(i).Name
 If IsNumeric(shtName) Then
 resultsArr(j, 1) = shtName
 j = j + 1
 End If
 Next
 With resultsWS
 .Columns(1).ClearContents
 If j > 1 Then
 .Range(.Cells(1, 1), .Cells(j - 1, 1)).Value = resultsArr
 End If
 End With
End Sub

For maximum flexibility/reusability, I'd probably make it a function instead of a sub. That way it can either write the results to a worksheet OR return the results as an array. I'd also probably change it so that it could check multiple workbooks simultaneously. That definitely complicates the code a bit, but potentially makes it useful in more situations.

Sub exampleMacro()
 'Checks sheets in specified workbooks, prints to specified worksheet
 Call listNumberedSheets(Array(ThisWorkbook, Workbooks("Example workbook.xlsx")), ThisWorkbook.Sheets("Results"))
 'Checks sheets in specified workbook, returns 1d array
 Dim arr As Variant
 arr = listNumberedSheets(ThisWorkbook)
End Sub
Function listNumberedSheets(wbToCheck As Variant, Optional resultsWS As Variant) As Variant
 Dim wbArr As Variant
 If Not IsArray(wbToCheck) Then
 wbArr = Array(wbToCheck)
 Else
 wbArr = wbToCheck
 End If
 Dim sheetCount As Long
 Dim i As Long
 For i = LBound(wbArr) To UBound(wbArr)
 If Not TypeName(wbArr(i)) = "Workbook" Then
 MsgBox "Error: wbToCheck must be either workbook or array of workbooks"
 Stop
 Exit Function
 Else
 sheetCount = sheetCount + wbArr(i).Sheets.Count
 End If
 Next
 ReDim resultsArr(1 To sheetCount, 1 To 1) As Double
 Dim j As Long
 Dim counter As Long
 counter = 1
 For i = LBound(wbArr) To UBound(wbArr)
 For j = 1 To wbArr(i).Sheets.Count
 Dim shtName As String
 shtName = wbArr(i).Sheets(j).Name
 If IsNumeric(shtName) Then
 resultsArr(counter, 1) = shtName
 counter = counter + 1
 End If
 Next
 Next
 If IsMissing(resultsWS) Then
 If j = 1 Then
 listNumberedSheets = ""
 Else
 ReDim finalArr(1 To counter - 1) As Double
 For i = LBound(finalArr) To UBound(finalArr)
 finalArr(i) = resultsArr(i, 1)
 Next
 listNumberedSheets = finalArr
 End If
 ElseIf TypeName(resultsWS) = "Worksheet" Then
 If j > 1 Then
 With resultsWS
 .Columns(1).ClearContents
 .Range(.Cells(1, 1), .Cells(counter - 1, 1)).Value = resultsArr
 End With
 End If
 End If
End Function
answered Oct 17, 2017 at 15:40
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.