For various reasons I prefer to use codenames to refer to worksheets in VBA code.
However, when I dynamically add a sheet like this (to a workbook that has just 1 sheet):
Sub example()
Worksheets.Add After:=Worksheets(Worksheets.Count)
'SOME OTHER CODE HERE
Sheet2.Activate
End Sub
... it won't compile because Sheet2 isn't defined as a variable.
Is there a way around this? At the moment I am reverting to using the sheet name:
Sheets("Sheet2").Activate
Is there a way to do it using the codename?
Thanks in advance.
It is a great idea to use codenames in VBA for sheets that exist at compile time. As you know, it allows you to refer to the sheet even if a user changes its name.
However, it's harder to do at runtime. You can do it using VBAProject Properties, if the user has granted access to the VBA project object model. But you can accomplish what you want, ongoing user-proof access to the sheet, simply by setting a WorkSheet variable:
Sub example()
Dim NewWorkSheet1 as Excel.Worksheet
Worksheets.Add After:=Worksheets(Worksheets.Count)
Set Newworksheet1 = ActiveSheet
'SOME OTHER CODE HERE
End Sub
Note that after adding a WorkSheet it is the ActiveSheet.
Also, you can declare the WorkSheet as a project- or module-level variable, if that level of scope is required.
Related
I am attempting to copy different images to different worksheets of my Workbook using the following code below. I am changing the Target Worksheet Dynamically in a different sub, with a String Variable in the Global Declarations section. I can see the variable being passed to the sub and in fact it works the first pass through the code, but when I attempt to change the "TargetSheetIni" variable to a new sheet, it continues to use the first original sheet as it loops through.
Can you not change a target sheet after using the Set keyword? Should I refer to the sheet directly instead?
Sub Test1()
Dim TargetWS, SourceWS As Worksheet
Set TargetWS = Worksheets(TargetSheetIni)
Set SourceWS = Worksheets("Images")
DoEvents
SourceWS.Shapes(CurrentImageId).Copy
DoEvents
TargetWS.Paste Range(ColumnLetter2 & RwCnter)
DoEvents
End Sub
I think I may have figured it out. As far as I can tell the issue may be that I used the Copy Sheet Functionality in Excel when I originally created the target sheets. And even though I renamed the sheets both on the tab below and in the project editor... for some reason VBA kept targeting only the original sheet
I proved this by changing my code around to explicitly call the sheet I wanted to target like so:
ActiveWorkbook.Worksheets("Sheet2").Paste Range("I2")
And even doing that it would target sheet 1 for the paste command instead of the expected sheet 2. I deleted the three copy sheets and created a new one from scratch and re-executed code and now it targets sheet 2 as expected.
I found this article that sort of explains it I guess...
https://www.spreadsheetsmadeeasy.com/7-common-vba-mistakes-to-avoid/
Ok my last answer may have not been correct. It appears as though for some reason inserting an ws.activate caused my code to start workin.g
Very frustrating fix. as I have always heard to avoid using that.
Hi so I am building an Excel system to run macro for running several different Macros on other Excel files.
Let's say I have two Excel files: "Parent.xlsx" and "child.xlsx" opened already.
In my Parent.xlsx I am running an VBA script, how can I run a macro called "method1" in my "PERSONAL.XLSB" for my "child.xlsx"
Right now, in my Parent.xlsx, I try to run this macro VBA script:
Workbooks("child.xlsx").Application.Run "PERSONAL.XLSB!method1"
In my PERSONAL.XLSB in Modelue6, I have:
Sub method1()
Dim rTable As Range
Selection.AutoFilter
End sub
Error:
Run-time error'1004':
AutoFilter method of Range class failed
Thank you very much!
You need to qualify and reference the workbook that the code should act on.
Look into the ActiveWorkbook property here: https://learn.microsoft.com/en-us/office/vba/api/excel.application.activeworkbook
Application.Run
In Parent.xlsx (to keep the code, save the file as e.g. Parent.xlsm)
Sub callMethod1()
Dim wb As Workbook
Set wb = Workbooks("child.xlsx")
Application.Run "PERSONAL.XLSB!method1", wb
End Sub
In PERSONAL.XLSB
Sub method1(wb As Workbook)
wb.Activate
If Not Selection Is Nothing Then
If TypeName(Selection) = "Range" Then
If ActiveSheet.AutoFilterMode Then
ActiveSheet.AutoFilterMode = False
End If
Selection.AutoFilter
End If
End If
End Sub
First off, you can't expect macros in a workbook of xlsx format. Make sure that you have eikther xlsm or xlsb as a source. This gets us to the second necessity. The workbook must have been saved to have such a format. You can't call a macro from an open workbook that hasn't been saved (because it doesn't have any format yet).
This is the correct syntax for calling an existing macro in another workbook.
Sub Test_TestCall()
Application.Run "'Personal.xlsb'!Method1"
End Sub
Add arguments to the call in brackets in the sequence required by the called procedure.
You may find it easier, however, to simply set a reference to the other workbook. Here is a step-by-step guide how to set a reference. Once a reference is set you can call all macros in the other project as if they were within the calling workbook. The reference gets saved with the workbook and will still be there when you next open the workbook. The drawback of this (and any other method) is that you can't send a working copy of the calling workbook to third parties unless you send the referenced workbook as well.
The error you get has yet another reason. The Selection is made by the user in the ActiveWorkbook, and since you don't tell Method1 which workbook is active it wouldn't be able to find it, right? However, this problem is best solved by following the most basic of all programming rules: "Avoid the Selection object!" Use the Range object instead. If you absolutely must use the Selection` object then pass it to your procedure as an argument.
Application.Run "'Personal.xlsb'!Method1(Selection)"
' and
Sub method1(MyRange As Range)
Dim rTable As Range
MyRange.AutoFilter
End sub
sheetCopy.Copy After:=ThisWorkbook.Worksheets(Worksheets.Count)
I use the code above to create a copy of a template worksheet in Excel.
Most of the time it creates the tab at the end which is what I want but sometime it creates new tab somewhere in the middle.
Is there a way to ensure it is copied to the end of the sheets?
(Thisworkbook.Worksheets.Count)
The issue
When working with a Workbook, Worksheet, Range, or other similar objects, it's best to avoid implicit member calls.
Most of these default to whatever is active. For instance, Worksheets.count is the same as ActiveWorkbook.Worksheets.count.
In your code you were almost there as you correctly used ThisWorkbook when accessing the Worksheets Collection; however, the Worksheets.count is defaulting to the ActiveWorkbook.
The Solution
To fix it, I like using With blocks to help shorten the code, and make it easier to be explicit in my refrences.
With ThisWorkbook
sheetCopy.Copy After:=.Worksheets(.Worksheets.Count)
End With
I have an excel spreadsheet with a very complex macro. The spreadsheet takes a file (imported through a button on the sheet), and runs statistics on it. is there any way to automate the macro to run on multiple files in a folder?
Thanks in advance
The macro identifies the worksheets from which it draws data and these sheets are in a workbook, which is also identified. Hence, by changing the name of the source(s) in the code, perhaps even dynamically, you can make the same macro perform its magic on other workbooks.
With that said, since VBA absolutely needs a workbooks to identify a worksheet and must have a worksheet in order to identify a cell, VBA will provide a default for either if the code doesn't mention another. Therefore the innocent Cells(1, 1) or Range("A1:B10") you may see in your code in fact stand for ActiveWorkbook.ActiveSheet.Cells(1, 1) or ActiveWorkbook.ActiveSheet.Range("A1:B10"). Therefore, if you want to change the default workbook for another the process is to first introduce a variable to specify the workbook and worksheet and then change the object assigned to that variable.
In a less generic way, let me presume that you don't have syntax like Range("A1:B10") in your code at all but Worksheets("Sheet1").Range("A1:B10"), identifying the worksheet but not the workbook. Let me further presume that the sheet names are the same in all the workbooks on which you want to run the code. In that case you would make the change as shown below.
Dim Wb As Workbook
Set Wb = ActiveWorkbook 'or perhaps ThisWorkbook (=the one having the code)
' and then change all applicable instances to
Wb.Worksheets("Sheet1").Range("A1:B10")
Now you can change the code to specify another workbook simply with:-
Set Wb = Workbooks("My workbook.xlsm")
I have several excel files that have timer and macros executing. But a big problem is when workbook A's macro is called, while workbook B is active. The macro is executed in the wrong book and failed.
Do I need to put windows().active at the beginning of every function?
If I have different modules how do I pass this workbook name to all of them?
This seems excessive and not right. Is there any good solution to this problem?
Looking forward to your answers
You are on the right track with this
2.If I have different modules how do I pass this workbook name to all of them
I assume that your macro is using the ActiveWorkbook property, or just using Worksheet properties like Range without qualifying them?
Instead of using ActiveWorkbook use ThisWorkbook. Instead of using Range use ThisWoorkbook.Worksheets(1).Range and so forth. Otherwise the macro will assume that the active worksheet is the one you want.
Sub MyMacro
Range("A1").Text = "Test"
End Sub
Try
Sub MyMacro(ByVal oWorksheet as Worksheet)
oWorksheet.Range("A1").Text = "Test"
End Sub
Then pass the worksheet object as a parameter.
You may also find the ThisWorkbook object useful - it is the workbook the macro resides in, or the Application.Caller object, which is the object calling the current macro, for example the Range object if it is a cell formula, or presumably the timer object in your case.
If your macros behave the way you described it, they probably depend explicitly or implicitly on
ActiveWorkbook
or
ActiveSheet
Those kind of dependencies should be avoided, if possible. The macro recorder produces such code, you should change it immediately whenever you have recorded a macro.
For example, if you have some code like
s = Range("A1").Value
Excel implicitly changes that to
s = ActiveSheet.Range("A1").Value
One can avoid that by accessing all cells, ranges, workbook parts etc. by explicitly using the right sheet or workbook object:
Dim sh as Worksheet
Set sh = .... ' Initialize sh the first time where the sheet is created or loaded
'later on:
s = sh.Range("A1").Value
By using a parameters of the form
sh as Worksheet, wb as workbook
for your subs and functions, you can pass the right sheet and workbook between modules, which answers your second question. And if you need access to the workbook where your macro resides, use ThisWorkbook.
I'd go one further... make sure your code doesn't have
Selection
or
ActiveCell
objects within them. You would need to rewrite these using the Range object.