I write Excel VBA reports that tap into SQL databases. I'm also in a company that uses dozens of such reports, and any user could have multiple such reports open at once from multiple different sources. Because of this, it is paramount that code does not affect or interfere with any other open workbooks.
My current report unavoidably uses volatile functions, so users that open and then immediately close the file will get prompted with a Save? dialog. Searching online has always pointed to using
Private Sub Workbook_Open()
ActiveWorkbook.Saved = True
End Sub
While effective, I've never been a big fan because of the non-specific nature of "ActiveWorkbook".
Question:
What are the pros/cons/pitfalls of using any of the following options-
ActiveWorkbook.Saved
ThisWorkbook.Saved
Me.Saved
Something_I_haven't_thought_of.Saved
ActiveWorkbook
I would argue that there are no pros to using ActiveWorkbook, as a direct reference to what you're working with should always be preferable, especially in the case stated above where multiple workbooks are involved. Even in the event of opening and working with arbitrary workbooks whose names you don't specifically know (via a FSO or Dir() loop), you can always set a workbook variable equal to the return value of a Workbook.Open function.
ThisWorkbook
Any time you have code working with more than one workbook or even code working with one workbook while multiple workbooks are open, I would recommend ThisWorkbook. It is versatile in that it can be used in object code modules and generic modules to return a consistent reference to the workbook it is placed within. There really aren't any cons to explicitly specifying ThisWorkbook when that's what you mean to reference.
Me
Me and ThisWorkbook are interchangeable when using them inside the ThisWorkbook object code module, but outside of that, Me refers to the object which it is placed within. For instance Me.Name inside a worksheet object code module will get you the name of the worksheet. Doing the same inside the workbook code module will get you the name of the workbook.
For the sake of clarity and re-usability, I would personally recommend ThisWorkbook out of all 3.
Active workbook is the workbook that is in use/activated (hence can change during code execution) and thus less reliable.
This workbook is the workbook on which the code is run. Hence, a good option if that is the workbook you want to refer to.
"Me" refers to where the code is. E.g. if in sheetmodule, it refers to that sheet, if in a userform module it refers to that module, if in thisworkbook module it is the same as 'thisworkbook', etc.
Defining a variable as workbook can refer to any open workbook. Can be redefined as desired in the code and hence very flexible as long as you know the name of the book
Application.DisplayAlerts = False can be used to suppress save/overwrite requests. Which can be very handy if you do not want users to receive a prompt.
Do re-enable as this is a powerful yet 'dangerous' piece of code.
Related
I have some VBA user-defined functions that I'd like to store in their own workbook so that they can be accessed by multiple other workbooks. I saw that an easy way to do it is to make the workbook with the commonly-used functions, then reference this common workbook using Tools > References (VBA window).
However, I now find that when I use that function in the calling workbook, it wants me to have the common-function workbook open. I don't want to do this, since that seems likely to create some confusion with my users.
What is best practice for sharing VBA functions between workbooks, without having to have the function "repository" open in Excel?
the workbook should be open, but you can make it invisible
Dim commonWB as Workbook
Set commonWB = Workbooks.Open("path/file.name")
commonWB .Windows(1).Visible = False
Don't forget to close it when you leave the macro
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
What is the best way of protecting a specific Excel workbook?
I have an inherited script that includes the following common lines at the end:
ActiveSheet.Protect "my-password"
ActiveWorkbook.Protect "my-password"
However, I've noticed that as the script can take a few minutes to run users often switch to a new unrelated workbook whilst it solves - whatever else they are working on. The password protection is then inherited by the unrelated workbook upon the completion of the macro - since whatever other Excel file the user is working within is now "Active" (presumably? this is my reading of the problem).
The above script is in a workbook that can be renamed to whatever the user chooses, and be saved in any number of directories. How can I ensure that only the original excel file is locked/unlocked by the Macro, when other workbooks are in use?
I am sure there are many ways to do this, but which is the most foolproof method?
NOTE: using office 365
Thanks Dean's answers in the comments:
Early in the code (and in Worksheet_Change if appropriate) enter the following to define your sheet as an object (named default_ws in my case):
Set default_ws = ActiveSheet
When you are ready to lock your sheet or workbook you can then use:
default_ws.Protect "password-here" 'protect your sheet
ThisWorkbook.Protect "password-here" 'protect your workbook
Also note:
You could also define your workbook as an object as follows if desired:
Set default_wb = ActiveWorkbook
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.
I have 10 XLS's, each of which contain a a few hundred lines of VBA that do the same thing. I want to move this common code into an 11th XLS, and have the other 10 call the code in the 11th XLS. The common code must have access to all of the data and worksheets in the calling XLS. This last requirement does not seem to be addressed by other answers to this question on SO. Can I pass the calling XLS's worksheets in as a parameter, or something similar?
Instead of putting this into a secondary XLS file, I'd recommend creating an XLA file (an Excel Add In).
This is the exact scenario for which XLA was intended. XLA will work the way you intend in this case.
For details on creating an XLA, see this page.
Yes, you can pass references to workbooks, worksheets, ranges, etc. as parameters to any function:
Public Sub CallMe(ByVal oWorkbook as Workbook)
Dim oWorksheet as Worksheet
Set oWorksheet = oWorkbook.Worksheets(1)
' Do stuff...
End Sub
Note that you'll probably have to re-write a lot of the code you copy from the 10 workbooks since they'll be full of implicit references to "this" workbook, such as Worksheets(1) etc. As in the example above, you now need to say oWorkbook.Workbooks(1) instead.