Proper procedure for declaring global variables in VBA? - vba

I have a macro workbook with a number of worksheets that exist permanently, which are constantly cleared, updated, etc. Since they are referred to in various subroutines, I have made each corresponding worksheet object a pseudo-global variable in the following manner, for example for the "Main" sheet:
Function MAIN() As Worksheet
Set MAIN = ThisWorkbook.Sheets("Main")
End Function
By doing so, I can then refer to each sheet in the other subroutines, for example:
MAIN.Cells.ClearContents
I have also defined some pseudo-global constants which are located in a fixed place on the "Main" sheet in a similar way, for example:
Function NumLines() As Integer
NumLines = MAIN.Range("C3").Value
End Function
In this way, I use "NumLines" just like any variable throughout the code.
I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?

For reliable sheet reference I would suggest to use Sheet.CodeName Property. Each sheet has its unique CodeName which you could find in the place marked yellow on the picture below.
For quick reference to cell value I would suggest to use Range name. After you select you C3 cell you need to put unique name in the box marked yellow below. All Range names are unique in the workbook.
As a result you can use sheet and cell reference as presented below in each of your subroutines in your project.
Sub Test_Macro()
Debug.Print MAIN.Name '>> result: Sheet1
Debug.Print Range("CellC3").Value '>> result: 100
End Sub

I expect that there is a more efficient way to manage globally accessed variables like these and was wondering, what would be a better way to accomplish this?
When I use global variables in VBA, I do three things.
I always preface global variables with a g_ prefix. It seems often that a global variable in VBA is useful. But I've also spent far too long trying to track down "what variables are global or not?" in other people's code. Keeping a very clear naming convention will save you and whoever looks at your code a TON of hassle in the future.
This is even more important if you are less experienced as a developer. Avoiding globals is hard in VBA, and the less experience you have, the more likely it is you will use globals. For others to help or maintain the code this becomes so important.
If you are going to be using even a small number of global variables, you must use Option Explicit unless you want to cause nightmares in maintaining code. It's hard enough to track down these errors when you wrote code let alone months or years later.
I always create a module which is called "GlobalVariables" or something similar. That module contains all of the global declarations in one location. For larger code bases this can become longer but it has always paid off for me because I know exactly where all my globals are defined. None of the "which file is this variable actually being defined in?" game.
Just an unrelated note, too, in your first example - I would use the code name rather than that function. Each VBA worksheet has a sheet name ("Main" in your case) as well as a codename, which you can set in VBA and remains the same. This prevents users from changing the name of "Main" and breaking code.
You can also refer directly to them similar to how you are using MAIN.Cells. KazJaw has a good example of this.

Related

Finding a VBAS defnied Named Range definition

a valueI've inherited a large VBA project and whilst I have lots of dev expereince I have a small amount of VBA. The code reads data off a sheet in the form:
Intersect(Range("colName"), .Rows(intCurrentRow)).Value
Where colName is a named range, or so I thought. I have searched all of the project code and the excel sheets and cannot find where colName is defined ?
So far I have searched the code, looked in Name Manager on the sheet and have googled furiously but hit a total blank. As I now need to read in another value from the Sheet I would really prefer to use the code that is currently used with another value instead of colName to reference my new data field.
Is there anything obvious I'm missing ?
Edits:
activesheet.range("colName").address gives a value of "$L:$l"
Its probably a hidden name.As Doug Glancy said, you can unhide it using VBA
Activeworkbook.Names("colName").Visible=True
If you are working with defined names you may find it useful to get My & Jan Karel Pieterse's Name Manager addin which (amongst many other things) handles hidden names. download from
http://www.decisionmodels.com/downloads.htm
It could be a hidden Name. Try:
ActiveWorkbook.Names("colName").Visible=True

How to find and edit the named range that a VBA variable is referring to?

My work has a Macro that we use to split combined mailing addresses out into multiple columns, but it is a little sloppy. I am looking to tighten up some of the search parameters, but I am not the one who initially wrote it so I am trying to figure some things out.
The thing I am looking at now is updating the city list in the Macro so that it will identify more cities. The trick is when I look at the Sheet that the process refers to, I cannot find an array or list with cities that the macro is checking against. It just has a bunch of sub processes that look empty to me. I am new to a lot of this so maybe I am missing something obvious.
The part of the module that references the worksheet looks like this:
CityList = shtCity.Range("CityList").Column
And the is no code in the module of the worksheet (shtCity)
I don't really know what I am looking at, so please let me know if there is any other information that I can collect to help resolve this.
Punch the following into the immediate window to see exactly where that range lives.
Debug.Print Range("CityList").Address
Alternative #1 - instead of using a debug.print you could put the same into a MSG box immediately before the module references that range
Alternative #2 - open shtCity and choose CTRL+F3 to see the named ranges.

Evaluate a relative reference from VBA relative to a known cell

I'm using the trick described here - !A1 - to get the the range of cells up to and including the current one (typically for a Rows() function). This works great for day to day usage, and eliminates a lot of errors that I get when moving ranges around when I previously had to use an adjacent set of rows.
Unfortunately, my formulas need to be evaluatable from VBA. With __THISCELL__ as my !A1 cell, and the cell housing the formula as $Z$100 the following evaluates to an error:
Application.Evaluate(rngCell.formula)
And the following evaluates to $A$1:$Z$50
rngCell.Worksheet.Evaluate(rngCell.formula)
Obviously an approach is to replace __THISCELL__ with rngCell.Address(External:=True) prior to evaluation, but here's the kicker: I'd like to be able to execute my formula parser in a workbook which uses, say THIS_CELL, THISCELL or __THISCELL safely, and I'd also like to be able to safely execute my code in a workbook with a name like __NOT__THIS_CELL__.
All I need for this is a mechanism to evaluate relative references relative to a specific cell address - which since people do use R1C1 references in VBA a fair bit, I imagine must be around. However, I don't know it. Can anyone help?
NB: I like to avoid fiddling with ActiveCell, Selection, etc. where possible, since those smell like the excel equivalent of SendKeys - who knows what the user is doing when you access them. Even then, though, I'm not certain I'll get the right answer, because for the Worksheet.Evaluate approach, I'm not positioned in cell $A$1!
If I understand your question, I believe you're looking for the Range().Offset method.
Range().Offset(rOffset, cOffset) refers to a range that is rOffset lower and cOffset to the right of the given range (negative values for up and left are allowed). Also, .Offset can access and set all of the properties of the range, just like you would do with .Range.
The approach I've taken for the time being is implicit in the question: when a named range is detected, store the current selection and worksheet, select the one which we use as the evaluation context, and then use Evaluate. This seems to work, provided the cell being evaluated is inside the activesheet.
I don't like jumping the selection all over the place - feels dirty - but short of a more elegant solution, it does work.

Excel VBA: Resetting spreadsheet count

I have a excel VBA macro that dynamically generates and deletes spreadsheets based on user input. However, when I open the VBA IDE, it seems that although I am naming my spreadsheets in the subs that create/delete them, the overall count is still increasing.
For example, depending on how far into execution my program is, under the "Microsoft Excel Objects" folder in my current project, the spreadsheets in the current workbook could look something like
Sheet101(Sheet3)
Sheet103(Sheet2)
Sheet104(Sheet1)
Or
Sheet81(Inputs)
Sheet83(Date Adjustment Interpolation)
Sheet84(Pricing)
Sheet85(Comparison)
No matter if I delete the rest of them and add one, it still picks up where the last highest one left off.
I don't know how many times this macro will be run and I'd feel a lot better about putting it out there if I could reset this annoying tally on the number of spreadsheets that have ever been generated, since I don't know for sure where excel will cut me off. Plus it's just annoying.
My Question:
I would like to know how to alter that spreadsheet number, or at least what the relevant object is for doing so.
Thanks!
Thanks to #dijkay s suggestion on code names, I've found some code to accomplish this.
ThisWorkbook.VBProject.VBComponents("Sheet1").name = "test"
Will change the code name of Sheet1 to test, so in the Excel Objects folder, it will appear as test(Sheet1) for example.
This option, however, requires messing around with some trust/security settings in each individual excel client running the macro, which is unsuitable for my purposes, unfortunately. You can also change the value manually by changing the (Name) property directly in the IDE through the properties window.
here are some ideas you can try...
Sheets(x).Name = "Sheet" & x
or (assuming in this example, 'Sheet3' doesn't already exist:
Set Sheet3 = sheets.Add
Sheet3.name = "Sheet3"
This is more cleanup than re-setting
cheers,
Micéal

How can I create sheet-specific named ranges without using Indirect?

I've come up with a useful trick, where I create a named range that refers to the current worksheet, by using the following formula:
=RIGHT(CELL("filename",INDIRECT("A1")), LEN(CELL("filename",INDIRECT("A1"))) - FIND("]",CELL("filename",INDIRECT("A1"))))&T(NOW())
Where the INDIRECTs are there ONLY to stop Excel from Converting A1 --> Sheet1!A1. This works beautifully until I need to call evaluate on it from VBA (which does happen).
Can anyone tell me how either (1) to evaluate a name with this formula in VBA or (2) to get a sheet non-specific reference into the formula. I'd rather not use VBA, since it'll get evaluated ~12000 times, and that's likely to be slow, but if need be, it's probably ok. However, please bear in mind that the sheet it is calculated from is quite unlikely to be ActiveSheet, so the context for the Range() function in VBA is a little tricky - hence why I'm asking in the first place.
One possible approach: use a simple UDF which just returns the name of the sheet it's called from.
Eg:
Function SheetName()
SheetName = Application.Caller.Parent.Name
End Function