How do I declare a global variable in VBA? - excel

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.

Related

Why can't I declare my Class Object as such?

I am currently creating a Class Object for a VBA file, its objective is to act as a range dictionary that can be passed single cells. If this cell is contained in one of the ranges, it returns the value associated to the corresponding range key. The class name is "rangeDic".
It is in the making so its functionalities are not implemented yet. Here's the code:
Private zone() As String
Private bounds() As String
Private link As Dictionary
Const ContextId = 33
'Init zone
Private Sub Class_Initialize()
Set link = New Dictionary
ReDim zone(0)
ReDim bounds(0)
End Sub
'properties
Property Get linkDico() As Dictionary
Set linkDico = link
End Property
Property Set linkDico(d As Dictionary)
Set link = d
End Property
Property Get pZone() As String()
pZone = zone
End Property
Property Let pZone(a() As String)
Let zone = a
End Property
'methods
Public Sub findBounds()
Dim elmt As String
Dim i As Integer
Dim temp() As String
i = 1
For Each elmt In zone
ReDim Preserve bounds(i)
temp = Split(elmt, ":")
bounds(i - 1) = temp(0)
bounds(i) = temp(1)
i = i + 2
Next elmt
End Sub
I was trying to instanciate it in a test sub in order to debug mid conception. Here's the code:
Sub test()
Dim rd As rangeDic
Dim ran() As String
Dim tabs() As Variant
Dim i As Integer
i = 1
With ThisWorkbook.Worksheets("DataRanges")
While .Cells(i, 1).Value <> none
ReDim Preserve ran(i - 1)
ReDim Preserve tabs(i - 1)
ran(i - 1) = .Cells(i, 1).Value
tabs(i - 1) = .Cells(i, 3).Value
i = i + 1
Wend
End With
Set rd = createRangeDic(ran, tabs)
End Sub
Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic
Dim obje As Object
Dim zonesL As Integer
Dim valsL As Integer
Dim i As Integer
zonesL = UBound(zones) - LBound(zones)
valsL = UBound(vals) - LBound(vals)
If zonesL <> valsL Then
Err.Raise vbObjectError + 5, "", "The key and value arrays are not the same length.", "", ContextId
End If
Set obje = New rangeDic
obje.pZone = zones()
For i = 0 To 5
obje.linkDico.add zones(i), vals(i)
Next i
Set createRangeDic = obje
End Function
Take a look at line 2 of Public Function createRangeDic. I have to declare my object as "Object", if I try declaring it as "rangeDic", Excel crashes at line obje.pZone = zones(). Upon looking in the Windows Event Log, I can see a "Error 1000" type of application unknown error resulting in the crash, with "VB7.DLL" being the faulty package.
Why so ? Am I doing something wrong ?
Thanks for your help
Edit: I work under Excel 2016
It looks like this is a bug. My Excel does not crash but I get an "Internal Error".
Let's clarify a few things first, since you're coming from a Java background.
Arrays can only be passed by reference
In VBA an array can only be passed by reference to another method (unless you wrap it in a Variant). So, this declaration:
Property Let pZone(a() As String) 'Implicit declaration
is the equivalent of this:
Property Let pZone(ByRef a() As String) 'Explicit declaration
and of course, this:
Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic
is the equivalent of this:
Public Function createRangeDic(ByRef zones() As String, ByRef vals() As Variant) As rangeDic
If you try to declare a method parameter like this: ByVal a() As String you will simply get a compile error.
Arrays are copied when assigned
Assuming two arrays called a and b, when doing something like a = b a copy of the b array is assigned to a. Let's test this. In a standard module drop this code:
Option Explicit
Sub ArrCopy()
Dim a() As String
Dim b() As String
ReDim b(0 To 0)
b(0) = 1
a = b
a(0) = 2
Debug.Print "a(0) = " & a(0)
Debug.Print "b(0) = " & b(0)
End Sub
After running ArrCopy my immediate window looks like this:
As shown, the contents of array b are not affected when changing array a.
A property Let always receives it's parameters ByVal regardless of whether you specify ByRef
Let's test this. Create a class called Class1 and add this code:
Option Explicit
Public Property Let SArray(ByRef arr() As String)
arr(0) = 1
End Property
Public Function SArray2(ByRef arr() As String)
arr(0) = 2
End Function
Now create a standard module and add this code:
Option Explicit
Sub Test()
Dim c As New Class1
Dim arr() As String: ReDim arr(0 To 0)
arr(0) = 0
Debug.Print arr(0) & " - value before passing to Let Property"
c.SArray = arr
Debug.Print arr(0) & " - value after passing to Let Property"
arr(0) = 1
Debug.Print arr(0) & " - value before passing to Function"
c.SArray2 arr
Debug.Print arr(0) & " - value after passing to Function"
End Sub
After running Test, my immediate window looks like this:
So, this simple test proves that the Property Let does a copy of the array even though arrays can only be passed ByRef.
The bug
Your original ran variable (Sub test) is passed ByRef to createRangeDic under a new name zones which is then passed ByRef again to pZone (the Let property). Under normal circumstances there should be no issue with passing an array ByRef as many times as you want but here it seems it is an issue because the Property Let is trying to make a copy.
Interestingly if we replace this (inside createRangeDic):
obje.pZone = zones()
with this:
Dim x() As String
x = zones
obje.pZone = x
the code runs with no issue even if obje is declared As rangeDic. This works because the x array is a copy of the zones array.
It looks that the Property Let cannot make a copy of an array that has been passed ByRef multiple times but it works perfectly fine if it was passed ByRef just once. Maybe because of the way stack frames are added in the call stack, there is a memory access issue but difficult to say. Regardless what the problem is, this seems to be a bug.
Unrelated to the question but I must add a few things:
Using ReDim Preserve in a loop is a bad idea because each time a new memory is allocated for a new (larger) array and each element is copied from the old array to the new array. This is very slow. Instead use a Collection as
#DanielDuĊĦek suggested in the comments or minimize the number of ReDim Preserve calls (for example if you know how many values you will have then just dimension the array once at the beginning).
Reading a Range cell by cell is super slow. Read the whole Range into an array by using the Range.Value or Range.Value2 property (I prefer the latter). Both methods returns an array as long as the range has more than 1 cell.
Never expose a private member object of a class if that object is responsible for the internal workings of the class. For example you should never expose the private collection inside a custom collection class because it breaks encapsulation. In your case the linkDico exposes the internal dictionary which can the be modified from outside the main class instance. Maybe it does not break anything in your particular example but just worth mentioning. On the other hand Property Get pZone() As String() is safe as this returns a copy of the internal array.
Add Option Explicit to the top of all your modules/classes to make sure you enforce proper variable declaration. Your code failed to compile for me because none does not exist in VBA unless you have it somewhere else in your project. There were a few other issues that I found once I turned the option on.

Error handling of multiple class attributes

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

Passing parameter into function to create variable name

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

Excel VBA reference issues

I was following some examples online in which im trying to pass a value from one sub into a another sub in VBA but get the error:
Compile Error:
Procedure declaration does not match description of event or procedure having the same name.
Sub next_sat_click()
Dim iweekday As Integer
Dim nextsat As String
Dim index As Integer
Dim str_game_date As String
iweekday = weekday(Now(), vbSunday)
nextsat = Format((Now + 7 - iweekday), "mm-dd-yy")
Call set_button_Click(nextsat)
End Sub
Sub set_button_Click(ByRef nextsat As String)
......
End Sub
Change the sub name in something else like SetButtonOnClick.
The _Click keyword is reserved by excel for the Click event on buttons if you have a button called with the same name.
You can't change the parameters for an event handler (except for the parameter name). That also means you can't add any parameters if none are expected. Not even Optional ByRef nextsat As String will work.
There are three ways to pass a value between event handlers in a UserForm:
Using a global variable (not recommended, ever);
Via the UserForm.tag property (recommended for simple values such as strings). Obviously cannot be used if it already has a permanent use;
Via one or more hidden controls (recommended for multiple or complex values as well as simple ones).
I've used the second method:
Sub next_sat_click()
Dim iweekday As Integer
Dim nextsat As String
Dim index As Integer
Dim str_game_date As String
iweekday = Weekday(Now(), vbSunday)
nextsat = Format((Now + 7 - iweekday), "mm-dd-yy")
Me.Tag = nextsat
End Sub
Sub set_button_Click()
Dim nextsat As String
nextsat = Me.Tag
......
End Sub
A better solution in your case might be to have a visible TextBox in which you store the calculated date when the user clicks next_sat, so that the user can see it. Then in your set_button handler, grab it from TextBox.Text.

VBA: Creating session persistent objects (hash) in Excel

Is it possible in a VBA function (UDF) to create an object that has global scope? I.e persists beyond the runtime of the function? I would want to stick it in a hash with a unique key that I can pass to other functions. I know you can do this in c#/c++ dll's.
The motivation is a heavy duty piece of processing that I don't want to repeat across hundreds of function calls: I want to cache the results so I only need to do once. E.g let's imagine I have a UDF which builds the results object in Cell A1:
=CreateResultsObject(arg1, arg2, arg3...)
The function does the heavy work and returns a unique ID string (the key for the object stored in the persistent hash). Cell A1 now contains this string value which I can then pass to other functions: they can then access the cached object in the hash with the key.
Is this possible? If so how?
The variables you declare in a module are persistent.
This code in a module might go into the direction you want:
Option Explicit
Dim col As New Collection
Public Function GetValue(ByVal strName As String) As String
GetValue = col.Item(strName)
End Function
Public Sub SetValue(ByVal strName As String, ByVal strValue As String)
col.Add strValue, strName
End Sub
Note:
For duplicate or missing names the code will fail.
Instead of a string value any kind of object could be passed by modifying the function signatures accordingly.
Addendum:
The same code with a bit more intelligence - for existing keys in the collection the value will be replaced instead of failing with an error.
Option Explicit
Dim col As New Collection
Public Function GetValue(ByVal strName As String) As String
GetValue = col.Item(strName)
End Function
Public Sub SetValue(ByVal strName As String, ByVal strValue As String)
If HasValue(strName) Then
col.Remove (strName)
End If
col.Add strValue, strName
End Sub
Private Function HasValue(ByVal strName As String) As Boolean
Dim val As Variant
Dim bRes As Boolean
bRes = True
On Error Resume Next
val = col.Item(strName)
If Err.Number <> 0 Then
bRes = False
Err.Clear
End If
On Error GoTo 0
HasValue = bRes
End Function
What about using a global variable within a module?
Something like this:
Option Explicit
Dim sHash As String
Function CreateResultsObject()
'very long code
sHash = "MyTest"
CreateResultsObject = "ok"
End Function
Function displayresultsobject()
displayresultsobject = sHash
End Function
Note that your Hash will be recalculated only when you call CreateResultsObject() in your worksheet and each time you ask for recalculation.

Resources