I'm updating a macro that's used in lots of spreadsheets, and it's rather slow. While looking to speed it up, I noticed that at one point it goes through this loop:
For each wsLoop in ThisWorkbook.Worksheets
wsLoop.Activate
With ActiveSheet.Status_Text
If bStatus = True Then
.ForeColor = &HC000&
.Caption = "ONLINE"
Else
.ForeColor = &HFF&
.Caption = "OFFLINE"
End If
End With
Next wsLoop
Where wsLoop is a worksheet, bStatus is a boolean and Status_Text is the name of an ActiveX label form control on each worksheet.
I know using .Activate is bad practice and can slow things down, so I dropped the wsLoop.Activate and changed the next line to With wsLoop.Status_Text, but now I get a "Method or data member not found" error message.
What's the proper way to do this?
Interesting question which seems to touch on some poorly-documented features of Excel VBA. It seems that maybe the expression ActiveSheet.Status_Text is operating as the name of the control, with the dot acting as a namespace qualifier, but that in wsLoop.Status_Text VBA is interpreting the dot as the method/property access operator and is correctly giving the error message that no such method or property exists. To reproduce the problem, I created a label named Status_Text on each sheet and then ran
Sub test1()
Dim ws As Worksheet
For Each ws In Worksheets
Debug.Print ws.Status_Text.Caption 'fails
Next ws
End Sub
It crashes with the error that you show. One workaround (although just why it works is mysterious) is to change the loop index from being a Worksheet to a Variant:
Sub test2()
Dim ws As Variant
For Each ws In Worksheets
Debug.Print ws.Status_Text.Caption 'succeeds
Next ws
End Sub
The odd thing about this last example is if you add the line Debug.Print TypeName(ws) in the for-each loop it prints Worksheet so ws.Status_Text works if ws is a variant which holds a worksheet but not if ws is actually a worksheet. The mystery is in some sense deepened but in another sense lessened when you step through this sub in the debugger, looking at the locals window. The type of ws in the loop is described as Variant/Object/Sheet1 (in the first pass through the loop). The specific sheet seems to be part of the current subtype of the variable.
Another workaround is to use a For-Next loop rather than a For-Each:
Sub test3()
Dim i As Long
For i = 1 To Worksheets.Count
Debug.Print Worksheets(i).Status_Text.Caption 'succeeds
Next i
End Sub
You could use either of these approaches to get a reference to the label without having to activate the sheet.
Related
I am struggling with proper syntax for setting variables as ranges...
Specifically, I'm testing a function I want to use in an app that creates new profiles and store the data, I will store that data on a hidden sheet, so they can be recalled at run time.
I'm currently construction a userform in order to create a new profile, the profile data needs to be stored to the first free column on the hidden sheet.
(where I will have to create a dynamic namedRange, so that i can use that range to save the associated data, and update the listbox in the userform)
Right now, I'm stumped by this:
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'another debug check - only works if sheet activated
Dim trgtCell As Range
Set trgtCell = ws.Cells(1, trgtCol) '<------- problem line
Debug.Print trgtCell '<----- prints "" to the immediate window.
End Sub
The LastColInSheet function is copied form Ron de bruin's page: https://www.rondebruin.nl/win/s9/win005.htm it simply returns a column number, in this case: 4.(One problem with it is if the sheet is empty, it returns an error, wondering if this can be fixed with an if statement in the function.)
I've tried many iterations of the problem line, some work but only if the storage sheet is activated, and give an error if not activate or selected, as the sheet will be hidden, I need this to work without activating the sheet, (although I could switch off screen activation?).
But I understand that it is best practice to avoid extraneous selects and activates, how can I just point directly to what I want and save that range into a variable?
It just doesn't seem like it should be so difficult, I must be missing something obvious.
It also seems like it shouldn't need so many lines of code to do something so simple.
I tried some more iterations of the "problem line" after some more searching...
-The real problem was with the debug.print line
Sub TestFindLastFunctions()
Dim wb As Workbook
Set wb = ThisWorkbook
'wb.activate 'shouldn't be neccesary
Dim ws As Worksheet
Set ws = sh_02CRepStorage
'ws.activate 'shoudn't be neccesary
Dim trgtCol As Long
trgtCol = LastColInSheet(ws) + 2
Debug.Print trgtCol ' so far so good
'Cells(1, trgtCol).Select 'debug Only works if already on sheet
Dim trgtCell As Range
'Set trgtCell = ws.Range _
(ws.Cells(1, trgtCol), ws.Cells(1, trgtCol))
' unnecessarily complex, but correct if using .range?
'but works if insisting on range
Set trgtCell = ws.Cells(1, trgtCol) 'back to original
Debug.Print trgtCell.Address '<---problem was here?
End Sub
I got the rest of my other sub working the way I want it to. It just highlights cells of interest and hides rows that are irrelevant to me across a number of worksheets. One of the last manual processes for me in this is copying a worksheet into the report every day to run the sub. I finally got it working as a standalone sub:
Sub OrbitAdd()
Dim wbOrbit As Workbook
Dim wbTop As Workbook
Set wbTop = ActiveWorkbook
Set wbOrbit = Workbooks.Open("C:\Users\*****\Orbit.xlsx")
wbOrbit.Sheets(1).Copy after:=wbTop.Sheets(7)
wbOrbit.Close SaveChanges:=False
End Sub
When I went to incorporate this into the main sub is when it broke. I tried a basic Call OrbitAdd() and it threw an error (I forget which error). I think it was looking for some arguments to pass to the sub maybe? When I tried to copy this in there directly it would run and add the worksheet and close the workbook before throwing the object required 424 error. As far as I can tell it didn't do anything after closing the workbook.
Why is this working as an independent sub, and not no matter how I try to incorporate it into the main sub? What am I missing or need to do to transition from this block of code back to the main sub to handle the error? (the **** in the file path are to obscure irrelevant file path info)
edit: adding the next few lines from my main sub for further troubleshooting help. The only thing before the first shared code block is turning off screen updating and dimensioning parameters for the rest of the sub.
'sets Orbit range to the size of the eNodeB site list
With Sheet8
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
Set Orbit = .Range("A1").Resize(lastRow, 1)
End With
'Clearing conditional formatting from the workbook.
For Each ws In ThisWorkbook.Worksheets
ws.Cells.FormatConditions.Delete
Next ws
'iterates each worksheet with the tables to apply the formatting.
For j = 1 To 3
If j = 1 Then
Set ws = Sheet2
ElseIf j = 2 Then
Set ws = Sheet3
I am trying to create a macro which searches for the word "Routing" in all sheets of my active workbook.
When it detects the first occurrence in one of my sheets of my workbook, I want my macro to select the related cell on the corresponding sheet.
My macro returns the error message:
run-time error 1004: activate method of range class failed…
It seems this line of code generates the issue: Loc.Activate
Sub FindAndExecuteRouting()
Dim Sh As Worksheet
Dim Loc As Range
For Each Sh In ActiveWorkbook.Worksheets
With Sh.UsedRange
Set Loc = .Cells.Find(What:="Routing")
If Not Loc Is Nothing Then
Loc.Activate
End If
End With
Next
End Sub
You can't Activate a Range unless it's on the ActiveSheet.
So you'd have to do Sh.Activate before you can do Loc.Activate. Note that normally you do not want to Activate a cell you already have a reference to, but when the goal is to select a cell so that the user can see the selection box around a specific cell... well, that's one of the very few legit use cases for Worksheet.Activate =)
Public Sub FindAndExecuteRouting()
Dim Sh As Worksheet
Dim Loc As Range
For Each Sh In ActiveWorkbook.Worksheets
With Sh.UsedRange
Set Loc = .Find(What:="Routing")
If Not Loc Is Nothing Then
Sh.Activate
Loc.Activate
Exit Sub
End If
End With
Next
End Sub
Note the Exit Sub: you want to stop looping (Exit For would work as well) once you've found one, otherwise you'll just successively select every result very quickly, and only the last one will end up selected.
Consider using an indenter to help keep indentation consistent!
I am working on a macro that will cycle through all of the sheets in the active workbook and will then clear a certain part of a particular worksheet, based on whether one of the relevant keywords is contained in the worksheet name. In each case the worksheet name will be different, but any I want to clear will contain one of the key words below.
I have set up a separate macro to clear the range of cells in each case. If the Worksheet name does not contain any of the keywords, I want the macro to move onto the next worksheet.
My ultimate aim is to be able to apply this to numerous different workbooks, as the project I am working on is split by region, with a separate Excel file per region.
The code I have been trying is below. There are no errors appearing when I run the code, the code does not seem to run either, in fact nothing at all happens!
Any guidance or advice would be greatly appreciated.
Sub Loop_Customer_Sheets()
Dim ws As Integer
Dim i As Integer
ws = ActiveWorkbook.Worksheets.Count
For i = 1 To ws
If ActiveSheet.Name Like "*ABC*" Then
Call ABCInfoClear
ElseIf ActiveSheet.Name Like "*DEF*" Then
Call DEFInfoClear
ElseIf ActiveSheet.Name Like "*GHI*" Then
Call GHIInfoClear
Else:
End If
Next i
End Sub
"Nothing at all happens" - fixing the issue with your code:
Your issue is that you are looping through the number of sheets, but you are only checking the ActiveSheet, which never changes! Replace your code with
ws = ActiveWorkbook.Worksheets.Count
For i = 1 To ws
With ActiveWorkbook.WorkSheets(i)
If .Name Like "*ABC*" Then
ABCInfoClear
ElseIf .Name Like "*DEF*" Then
DEFInfoClear
ElseIf ActiveSheet.Name Like "*GHI*" Then
GHIInfoClear
End If
End With
Next i
Note: you don't need the Call keyword, you can just call subs as presented above.
Alternative solutions
A better option than having numerous macros might be to create a generic sub like
Sub ClearRangeInSheet(rangeAddress As String, sh As WorkSheet)
Dim myRange As Range
Set myRange = sh.Range(rangeAddress)
myRange.ClearContents
' Any other cell clearing code e.g. for formatting here
End Sub
Then call in the loop
Dim wsCount as Long
wsCount = ActiveWorkbook.WorkSheets.Count
For i = 1 to wsCount
With ActiveWorkbook
If .WorkSheets(i).Name Like "*ABC*" Then
' Always pass ".WorkSheets(i)", but change the range address as needed
ClearRangeInSheet("A1:A20", .WorkSheets(i))
ElseIf ' Other worksheet name conditions ...
End If
End With
Next I
As suggested in the comments, you could ditch indexing the sheets, and just loop through the sheet objects themselves:
Dim wksht as WorkSheet
For Each wksht In ActiveWorkbook.WorkSheets
If wksht.Name Like "*ABC*" Then
' Always pass wksht but change the range address as needed
ClearRangeInSheet("A1:A20", wksht)
ElseIf ' Other worksheet name conditions ...
End If
Next wksht
I've shortened my code a bit for the purposes of the question, but the error I'm getting is the same.
When trying to select the cells with data in column A on each worksheet and doing stuff with it, I get an error after the first worksheet:
Sub quickSub()
Dim sh As Worksheet
For Each sh In Worksheets
sh.Range("A6", Range("A6").End(xlDown)).Select
''Random bits of code here where I manipulate selection on each worksheet
Next
End Sub
The error I get is:
"Run-time error '1004': Method 'Range' of object'_Worksheet' failed.
Try this:
sh.Activate
sh.Range("A6", "A" & sh.Range("A6").End(xlDown).row).Select
I made sure the range reference was end downing on the right sheet
And I had the end down return the final row number and concatenate with the column letter which may not be needed, but may make it easier for you to debug.
Update:
Added activate line. Selection may require the sheet be active.
Update2:
Here's the 'Right' way to do this WITHOUT using select
Using this method directly referneces the worksheet data INSTEAD of needing to move around worksheet by worksheet.
This best practice will increase your code performance
Sub quickSub()
Dim sh As Worksheet
For Each sh In Worksheets
With sh.Range("A6", "A" & sh.Range("A6").End(xlDown).row)
'- lines that manipulate the 'selection' in the above with
.Value = "NewValue"
.font.bold = true
End With
''Random bits of code here where I manipulate selection on each worksheet
Next
End Sub