I would like to define a global variable in VBA in one module and use this in other VBA modules.
I am trying to follow: How do I declare a global variable in VBA?
I have created a new module called "GlobalVariables", and first declared the Public variables and then set their value within a function (trying to do this in open code causes an error). My code is below.
But the Global variable StartYear does not seem to be available into other VBA modules. What am I doing wrong?
Option Explicit
Public StartYear As Integer
Public BaseYear As Integer
Function DeclareGlobalVariables()
StartYear = ActiveWorkbook.Worksheets("RunModel").Range("StartYear").Value
BaseYear = ActiveWorkbook.Worksheets("RunModel").Range("BaseYear").Value
End Function
Make sure you put your golobal variable in a module and not in worksheet scope, to make it accessible in other modules.
Your Function should be a Sub because it does not return anything.
Your code will error if your cells eg. contain text (strings). Never trust a user's input. Always validate!
So I suggest the following
Module 1
Option Explicit
Public StartYear As Long
Public BaseYear As Long
Public Function InitializeGlobalVariables() As Boolean
InitializeGlobalVariables = True
With ActiveWorkbook.Worksheets("RunModel").Range("StartYear")
If IsYear(.Value) Then
StartYear = CLng(.Value)
Else
InitializeGlobalVariables = False
MsgBox "StartYear needs to be a number"
End If
End With
With ActiveWorkbook.Worksheets("RunModel").Range("BaseYear")
If IsYear(.Value) Then
BaseYear = CLng(.Value)
Else
InitializeGlobalVariables = False
MsgBox "BaseYear needs to be a number"
End If
End With
End Function
'validate if the input value is a valid year
Private Function IsYear(ByVal InputValue As Variant) As Boolean
If IsNumeric(InputValue) Then
If CLng(InputValue) = InputValue And _
InputValue > 0 And InputValue < 9999 Then 'integer not decimal AND 4 digit year
IsYear = True
End If
End If
End Function
And you can access the variables in any other module like:
Module 2
Option Explicit
Public Sub TestOutput()
'before using the variables test if they are initialized (not 0)
If StartYear = 0 Or BaseYear = 0 Then
'they are not initalized so initalize them (and at the same time check if it was successful)
If InitializeGlobalVariables = False Then
'the function returns FALSE if the initialization process failed so we need to cancel this procedure or we use not initilized variables!
MsgBox "Variables were not intitialized. Trying to initialize them failed too. I cannot proceed."
Exit Sub
End If
End If
Debug.Print StartYear
Debug.Print BaseYear
End Sub
Related
I have a class that holds data in separate attributes (I chose this design instead of an array or scripting.dictionary because I will be using these data for the construction of a decision tree and I want to make use of IntelliType while I construct it.).
The data are loaded from an excel spreadsheet in the form of floats, so I am storing them in a long datatype, but from time to time it happens so that a value is missing and is replaced by an "NA" string.
I would like to create an error-handling routine, that, if a non-numeric value is encountered, would replace the content with a value -1.
I know I could do this with checking with IsNumeric() or error-handling, but I don't know how to make it work for every one of the many attributes the class holds and I don't really like the solution to write a specific error-handling code for every one of them (storing each of them in a separate attribute is not elegant as well, but I find this a price I am willing to pay for the advantage of shorter syntax in the decision tree).
Is there a way to pass a value to a variable, that just encountered a type-mismatch error, by the error-handling code independent of the variable name?
A simple example with several of the attributes:
Option Explicit
Dim B_LYM As Long
Dim B_mem As Long
Dim B_CXCR3 As Long
Dim B_CXCR4_MFI As Long
Public Sub SetData(data as Range)
On Error Goto err_handler
B_LYM = data.Cells(1, 1)
B_mem = data.Cells(1, 2)
B_CXCR3 = data.Cells(1, 3)
B_CXCR4_MFI = data.Cells(1, 4)
err_handler:
'I need something like this:
'if valuebeingstored = "NA" then targetvariable = -1
End Sub
It could be that some other approach could be better and I am gladly open to options, I only want to emphasize that I would really like make use of IntelliType when constructing the decision tree. I was considering using scripting.dictionary, but the syntax will bloat the code very quickly.
You said you have a Class and therefore could include the function to check the input and return -1 inside the class and use the Get and Let properties to call the function.
Here is an example class (named clsDataStuff) demonstrating this:
Option Explicit
Private c_B_LYM As Double
Private c_B_mem As Double
Private c_B_CXCR3 As Double
Private c_B_CXCR4_MFI As Double
Public Property Let B_LYM(varValue As Variant)
c_B_LYM = ParseDouble(varValue)
End Property
Public Property Get B_LYM()
B_LYM = c_B_LYM
End Property
Public Property Let B_mem(varValue As Variant)
c_B_mem = ParseDouble(varValue)
End Property
Public Property Get B_mem()
B_mem = c_B_mem
End Property
Public Property Let B_CXCR3(varValue As Variant)
c_B_CXCR3 = ParseDouble(varValue)
End Property
Public Property Get B_CXCR3()
B_CXCR3 = c_B_CXCR3
End Property
Public Property Let B_CXCR4_MFI(varValue As Variant)
c_B_CXCR4_MFI = ParseDouble(varValue)
End Property
Public Property Get B_CXCR4_MFI()
B_CXCR4_MFI = c_B_CXCR4_MFI
End Property
Private Function ParseDouble(varValue As Variant) As Double
If IsNumeric(varValue) Then
ParseDouble = CDbl(varValue)
Else
ParseDouble = -1
End If
End Function
Noting that:
the Let property expects a Variant because you say your input could be a number, or a string
the Get property returns Double as you said your inputs are floats so Double is better than Long
the ParseDouble function simply checks for a numeric input and returns -1 otherwise
Then, in your module code:
Option Explicit
Dim B_LYM As Long
Dim B_mem As Long
Dim B_CXCR3 As Long
Dim B_CXCR4_MFI As Long
Public Sub Test()
Dim objDataStuff As clsDataStuff
Set objDataStuff = New clsDataStuff
objDataStuff.B_LYM = 1 'data.Cells(1, 1)
objDataStuff.B_mem = 2 'data.Cells(1, 2)
objDataStuff.B_CXCR3 = "a" 'data.Cells(1, 3)
objDataStuff.B_CXCR4_MFI = True 'data.Cells(1, 4)
Debug.Print objDataStuff.B_LYM
Debug.Print objDataStuff.B_mem
Debug.Print objDataStuff.B_CXCR3
Debug.Print objDataStuff.B_CXCR4_MFI
End Sub
Returns an output of:
1
2
-1
-1
Intellisense is available and you get validation of the input:
Edit - regarding the comment on dynamically setting a target variable.
Your class can be:
Option Explicit
Public B_LYM As Double
Public B_mem As Double
Public B_CXCR3 As Double
Public B_CXCR4_MFI As Double
Public Sub SetVar(ByVal strVarName As String, ByVal varValue As Variant)
Dim dblValue As Double
Dim strToEval As String
If Not MemberExists(strVarName) Then Exit Sub
dblValue = ParseDouble(varValue) ' do the parse
CallByName Me, strVarName, VbLet, dblValue ' dynamically assign the value
End Sub
Private Function ParseDouble(varValue As Variant) As Double
If IsNumeric(varValue) Then
ParseDouble = CDbl(varValue)
Else
ParseDouble = -1
End If
End Function
Private Function MemberExists(strVarName) As Boolean
Dim blnTest As Boolean
Dim varValue As Variant
On Error GoTo ErrHandler
varValue = CallByName(Me, strVarName, VbGet)
blnTest = True
GoTo ExitFunction
ErrHandler:
blnTest = False
ExitFunction:
MemberExists = blnTest
End Function
Where:
All the variables are Public and you still get Intellisense but avoid all the repetitive Let and Get code
A single SetVar method uses CallByName to dynamically set a target variable
Two problems:
You need the clunky MemberExists function to prevent SetVar trying to assign a value to a member that does not exist - otherwise this generates an error (438) but perhaps this is something you need in your logic ?
You can still assign values to the target variable with e.g. objDataStuff.B_CXR3 = "foo" which alsos produces an error for anything other than a number.
The example code shows the problem below. But sticking with SetVar method will produce the same output as above.
Option Explicit
Dim B_LYM As Long
Dim B_mem As Long
Dim B_CXCR3 As Long
Dim B_CXCR4_MFI As Long
Public Sub Test()
Dim objDataStuff As clsDataStuff
Set objDataStuff = New clsDataStuff
objDataStuff.SetVar "B_LYM", 1
objDataStuff.SetVar "B_mem", 2
objDataStuff.SetVar "B_CXCR3", -1
objDataStuff.SetVar "B_CXCR4_MFI", True
objDataStuff.SetVar "foobar", 999
' working around SetVar here generates an error
objDataStuff.B_CXCR3 = "bad"
Debug.Print objDataStuff.B_LYM
Debug.Print objDataStuff.B_mem
Debug.Print objDataStuff.B_CXCR3
Debug.Print objDataStuff.B_CXCR4_MFI
End Sub
I want to use global variables in my workbook and in the ThisWorkbook code. I declared the following varaibles
Public position_1 as string
Public position_2 as string
If I want to see the value of those variables I believe they need to be fully qualified so
Debug.Print ThisWorkbook.position_1
Debug.Print ThisWorkbook.position_2
I have written a UDF which I will pass in an integer to represent which variable I am looking for. I will only be passing in a single number and not a full variable name. I am trying to find a way to use this integer to concatenate with "position_" to display the value of the global variable, ThisWorkbook.position_1, ThisWorkbook.position_2, etc.
Function Test_Global_Var(position as Integer)
Dim variable_name As String
variable_name = "position_" & position
Debug.Print ThisWorkbook.variable_name
End Function
So when I call
Test_Global_Var(1)
my immediate window should display the value of
ThisWorkbook.position_1
The code below produces the following debug output
2 values defined.
ThisWorkbook.Position(0)
First Value
ThisWorkbook.Position(1)
Second Value
It uses a private array in the workbook named m_position. The contents are accessed by a global property ThisWorkbook.Position(index).
In a module have the following code:
Option Explicit
Public Sub Test()
If ThisWorkbook.NoValues Then
ThisWorkbook.FillValues "First Value", "Second Value"
End If
Debug.Print CStr(ThisWorkbook.Count) & " values defined."
Test_Global_Var 0
Test_Global_Var 1
End Sub
Public Sub Test_Global_Var(ByVal index As Long)
' Part of a UDF
Debug.Print "ThisWorkbook.Position(" & CStr(index) & ")"
Debug.Print ThisWorkbook.Position(index)
End Sub
In ThisWorkbook have the following code:
Option Explicit
Private m_position() As Variant
Private Sub Workbook_Open()
Call DefaultValues
End Sub
Public Property Get Position(ByVal index As Long) As Variant
Position = m_position(index)
End Property
Public Sub DefaultValues()
m_position = Array("First", "Second")
End Sub
Public Sub FillValues(ParamArray args() As Variant)
m_position = args
End Sub
Public Property Get Count() As Long
Count = UBound(m_position) - LBound(m_position) + 1
End Property
Public Property Get NoValues() As Boolean
On Error GoTo ArrUndefined:
Dim n As Long
n = UBound(m_position)
NoValues = False
On Error GoTo 0
Exit Sub
ArrUndefined:
NoValues = True
On Error GoTo 0
End Property
PS. In VBA never use Integer, but instead use Long. Integer is a 16bit type, while Long is the standard 32bit type that all other programming languages consider as an integer.
It is possible to consider a global dictionary variable and pass data through it from the UDF.
First add reference to Microsoft Scripting Runtime:
Thus, build the dictionary like this:
Public myDictionary As Dictionary
To initialize the myDictionary variable, consider adding it to a Workbook_Open event:
Private Sub Workbook_Open()
Set myDictionary = New Dictionary
End Sub
Then the UDF would look like this:
Public Function FillDicitonary(myVal As Long) As String
If myDictionary.Exists(myVal) Then
myDictionary(myVal) = "position " & myVal
Else
myDictionary.Add myVal, "position " & myVal
End If
FillDicitonary = "Filled with " & myVal
End Function
And it would overwrite every key in the dictionary, if it exists. At the end, the values could be printed:
Public Sub PrintDictionary()
Dim myKey As Variant
For Each myKey In myDictionary
Debug.Print myDictionary(myKey)
Next
End Sub
After a lot of small sub() writing in the same Excel workbook, I realised that I often used same part code, variables and constants. Thus I decided to write funcions() for the code, and declare variables & constant/static as Public outside functions and sub. I am very new to vba declarations and this is not so easy. Let me give you one summary of what i want to achieve. I have writen all funcions and sub in one module under the module directory of the workbook.
Option Explicit
Public ToDate As String ' variable I use in many sub and functions
Public MyPath As String ' variable I use in many sub and functions
Public NameOfWorker As Variant ' constant I use in many sub and functions
Public Salary As Double ' constant I use in many sub and functions
NameOfWorker = Cells(14, 19) ' !!! PB : 14 is highlighed with error : incorrect instruction outside a procedure
Salary = Cells(20, 7).Value '!!! same as above
How and where shall I declare such constants/statics ? Shall I write a "special" procedure to declare all these variables and constants ? I tried many way to declare them with no success.
Public Static NameOfWorker = Cells(14, 19) As String ' not working
''''''
Public Static nameOfWorker As String
NameOfWorker = Cells(14, 19) ' not working
''' etc etc
Thank you for help.
EDIT : after more reading, I found one solution this way:
Public Const MY_PATH = "Y:\path\to\directory\"
Public Const WORKERNAME = "14, 19"
Not so bad :-)
You could create a new module called something like DataHelper which looks like this:
Private NameOfWorker As String
Private AgeOfWorker As Long
Private SetupComplete As Boolean
Public Function GetNameOfWorker()
If NameOfWorker = "" Then
NameOfWorker = Sheets("SomeSheet").Cells(14, 19)
End If
GetNameOfWorker = NameOfWorker
End Function
Public Function GetAgeOfWorker()
...
End Function
Now in any other code you can retreive the value:
Sub SomeMethod()
Cells(1, 1).Value = DataHelper.GetNameOfWorker()
End Sub
...and you never have to worry if it's been set.
Good question!
I would Dim the globals above all the subs in the module, but initialize the globals at a convenient spot within some sub. For example:
Public NameOfWorker As String
Public AgeOfWorker As Long
Public SetupComplete As Boolean
Sub MAIN()
If SetupComplete Then
Else
NameOfWorker = Sheets("Sheet1").Range("B9")
AgeOfWorker = Sheets("Sheet1").Range("B10")
SetupComplete = True
MsgBox "Global variable set up complete!"
End If
End Sub
I wrote the following code:
Function find_results_idle()
Public iRaw As Integer
Public iColumn As Integer
iRaw = 1
iColumn = 1
And I get the error message:
"invalid attribute in Sub or Function"
Do you know what I did wrong?
I tried to use Global instead of Public, but got the same problem.
I tried to declare the function itself as `Public, but that also did no good.
What do I need to do to create the global variable?
You need to declare the variables outside the function:
Public iRaw As Integer
Public iColumn As Integer
Function find_results_idle()
iRaw = 1
iColumn = 1
This is a question about scope.
If you only want the variables to last the lifetime of the function, use Dim (short for Dimension) inside the function or sub to declare the variables:
Function AddSomeNumbers() As Integer
Dim intA As Integer
Dim intB As Integer
intA = 2
intB = 3
AddSomeNumbers = intA + intB
End Function
'intA and intB are no longer available since the function ended
A global variable (as SLaks pointed out) is declared outside of the function using the Public keyword. This variable will be available during the life of your running application. In the case of Excel, this means the variables will be available as long as that particular Excel workbook is open.
Public intA As Integer
Private intB As Integer
Function AddSomeNumbers() As Integer
intA = 2
intB = 3
AddSomeNumbers = intA + intB
End Function
'intA and intB are still both available. However, because intA is public, '
'it can also be referenced from code in other modules. Because intB is private,'
'it will be hidden from other modules.
You can also have variables that are only accessible within a particular module (or class) by declaring them with the Private keyword.
If you're building a big application and feel a need to use global variables, I would recommend creating a separate module just for your global variables. This should help you keep track of them in one place.
To use global variables, Insert New Module from VBA Project UI and declare variables using Global
Global iRaw As Integer
Global iColumn As Integer
The question is really about scope, as the other guy put it.
In short, consider this "module":
Public Var1 As variant 'Var1 can be used in all
'modules, class modules and userforms of
'thisworkbook and will preserve any values
'assigned to it until either the workbook
'is closed or the project is reset.
Dim Var2 As Variant 'Var2 and Var3 can be used anywhere on the
Private Var3 As Variant ''current module and will preserve any values
''they're assigned until either the workbook
''is closed or the project is reset.
Sub MySub() 'Var4 can only be used within the procedure MySub
Dim Var4 as Variant ''and will only store values until the procedure
End Sub ''ends.
Sub MyOtherSub() 'You can even declare another Var4 within a
Dim Var4 as Variant ''different procedure without generating an
End Sub ''error (only possible confusion).
You can check out this MSDN reference for more on variable declaration and this other Stack Overflow Question for more on how variables go out of scope.
Two other quick things:
Be organized when using workbook level variables, so your code doesn't get confusing. Prefer Functions (with proper data types) or passing arguments ByRef.
If you want a variable to preserve its value between calls, you can use the Static statement.
If this function is in a module/class, you could just write them outside of the function, so it has Global Scope. Global Scope means the variable can be accessed by another function in the same module/class (if you use dim as declaration statement, use public if you want the variables can be accessed by all function in all modules) :
Dim iRaw As Integer
Dim iColumn As Integer
Function find_results_idle()
iRaw = 1
iColumn = 1
End Function
Function this_can_access_global()
iRaw = 2
iColumn = 2
End Function
Also you can use -
Private Const SrlNumber As Integer = 910
Private Sub Workbook_Open()
If SrlNumber > 900 Then
MsgBox "This serial number is valid"
Else
MsgBox "This serial number is not valid"
End If
End Sub
Its tested on office 2010
The best way I find is to assign a property to the Workbook
It's scope remains valid as long as the workbook is open
Public WhenOpened As Date
Private Sub Workbook_Open()
ThisWorkbook.WhenOpened = Now()
End Sub
Create a public integer in the General Declaration.
Then in your function you can increase its value each time.
See example (function to save attachements of an email as CSV).
Public Numerator As Integer
Public Sub saveAttachtoDisk(itm As Outlook.MailItem)
Dim objAtt As Outlook.Attachment
Dim saveFolder As String
Dim FileName As String
saveFolder = "c:\temp\"
For Each objAtt In itm.Attachments
FileName = objAtt.DisplayName & "_" & Numerator & "_" & Format(Now, "yyyy-mm-dd H-mm-ss") & ".CSV"
objAtt.SaveAsFile saveFolder & "\" & FileName
Numerator = Numerator + 1
Set objAtt = Nothing
Next
End Sub
A good way to create Public/Global variables is to treat the Form like a class object and declare properties and use Public Property Get [variable] to access property/method. Also you might need to reference or pass a Reference to the instantiated Form module. You will get errors if you call methods to forms/reports that are closed.
Example: pass Me.Form.Module.Parent into sub/function not inside form.
Option Compare Database
Option Explicit
''***********************************''
' Name: Date: Created Date Author: Name
' Current Version: 1.0
' Called by:
''***********************************''
' Notes: Explain Who what when why...
' This code Example requires properties to be filled in
''***********************************''
' Global Variables
Public GlobalData As Variant
''***********************************''
' Private Variables
Private ObjectReference As Object
Private ExampleVariable As Variant
Private ExampleData As Variant
''***********************************''
' Public properties
Public Property Get ObjectVariable() As Object
Set ObjectVariable = ObjectReference
End Property
Public Property Get Variable1() As Variant
'Recommend using variants to avoid data errors
Variable1 = ExampleVariable
End property
''***********************************''
' Public Functions that return values
Public Function DataReturn (Input As Variant) As Variant
DataReturn = ExampleData + Input
End Function
''***********************************''
' Public Sub Routines
Public Sub GlobalMethod()
'call local Functions/Subs outside of form
Me.Form.Refresh
End Sub
''***********************************''
' Private Functions/Subs used not visible outside
''***********************************''
End Code
So in the other module you would be able to access:
Public Sub Method1(objForm as Object)
'read/write data value
objForm.GlobalData
'Get object reference (need to add Public Property Set to change reference object)
objForm.ObjectVariable
'read only (needs Public property Let to change value)
objForm.Variable1
'Gets result of function with input
objForm.DataReturn([Input])
'runs sub/function from outside of normal scope
objForm.GlobalMethod
End Sub
If you use Late Binding like I do always check for Null values and objects that are Nothing before attempting to do any processing.
I have the following function in VBA:
Private Function Option1Checked(option1 As OptionButton) As Integer
option1.ForeColor = vbGreen
If (option1.Value = True) Then
Option1Checked = 1
End If
Option1Checked = 0
End Function
Whenever I try to call the function like this
counter = counter + Option1Checked(OptionButton1)
I get a type mismatch error at runtime. But OptionButton1 is OptionButton, so what am I doing wrong?
You're running into one of the 'features' of VBA here. If you refer to some objects, like the option button, without a property specified, VBA assumes you want the default property, not the object itself. In the case of the option button, the default property is .Value, so in your code, OptionButton1 is not the option button object, but rather TRUE or FALSE depending on whether or not the OptionButton1 is checked.
Your best bet will be to change your function to this:
Private Function Option1Checked(option1 As Boolean) As Integer
//option1.ForeColor = vbGreen
If (option1 = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
The downside here is that you cannot change the foreground color of the option button to green without referring to it by name.
An alternative that would get you the functionality that you want would be to pass the name of the option button to your Function.
Private Function Option1Checked(ByVal option1 As String) As Integer
UserForm1.Controls(option1).ForeColor = vbGreen
If (UserForm1.Controls(option1) = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
Sub MyCountingRoutine()
Dim str As String
str = OptionButton1.Name
counter = counter + Option1Checked(str)
End Sub
Make sure you include the Else in the If..Then statement in your function, otherwise you will always get 0 back.