VBA value persistence - vba

I have a bunch of globally defined variables in my VBA (macro) script. I set values to these in a procedure in my current module. Is there any way to get these values in another procedure present in another module.

As far as variable declaration goes.
Within a sub - this is only accessible to the sub itself:
Sub LocalScope()
Dim stringVariable as string
stringVariable = "abc"
debug.print stringVariable
End Sub
Dim at the top of the module - this is accessible to any subs within the module:
Dim stringVariable as string
Sub ModuleScope()
stringVariable = "abc"
End Sub
Sub PrintString()
debug.print stringVariable
End Sub
Public at the top of the module - this is accessible to subs in all modules:
Public stringVariable as string
Sub ModuleScope()
stringVariable = "abc"
End Sub
Sub PrintString()
debug.print stringVariable
End Sub

Related

Call module with multiple inputs

I've tried finding a solution to this problem but have been unable to.
In generic form:
Module1
Sub Source()
Call Module2.Run
End Sub
Module2
Sub Run()
Value = 10
Some code which uses Value as input
End Sub
What I want to be able to do is to be able to define multiple Values in Module1 and then run Module2.Run() with each value.
Module1
Sub Source()
Value = 10, 20, 30
Call Module2.Run (10)
Call Module2.Run (20)
Call Module2.Run (30)
End Sub
Module2
Sub Run()
Value = Input from Module1.Source()
Some code which uses Value as input
End Sub
Or something along these lines. Any input would be greatly appreciated.
You can pass parameters through arguments like so
Sub Sub1 ()
Dim myVal as Long
myVal = 1000
Sub2 (myVal) 'The "Call" is not necessary
End Sub
Sub Sub2 (myVal as Long) 'This sub requires an input to run (myVal)
MsgBox myVal
End Sub
You can create an array, fill it and pass as an argument.
Avoid using names like Source and Run which are already used by Excel;.
Option Explicit
Sub Sour()
Dim arr_1d() As Variant
arr_1d = Array("val1", "val2", "val3")
Dest arr_1d
End Sub
Sub Dest(arr_1d() As Variant)
Dim y As Long
For y = LBound(arr_1d) To UBound(arr_1d)
Debug.Print arr_1d(y)
Next
End Sub

Excel vba nested inner subs like in python, java

In other programming languages there is a way to put some code into a separate function that gets called from the first one, for instance, in java:
public void exercise() {
run();
jump();
walk();
}
Is there a way in vba to put subs into one another? I tried to
Sub test1()
test2()
End Sub
Sub test2()
MsgBox("test2")
End Sub
But it gives mistake.
You would do this without parentheses:
Sub test1()
test2
End Sub
Parentheses are needed to enclose arguments in order to assign the output of a function to a variable. If your sub took arguments, you would do like:
Sub test1()
test2 "Hello", "World"
End Sub
Sub test2(arg1, arg2)
Msgbox arg1 & " - " & arg2
End Sub
You need to use call to call the subroutine:
call test2()

VBA ByRef Error Passing a Class Object to a Sub

I'm trying to pass an object to a new sub but keep hitting a ByRef Mismatch error.
I've declared my object as:
Dim targetWorkbook
Set targetWorkbook = New CWorkbooks
I'm calling my sub by using:
checkbook targetWorkbook
And my sub is set as:
Sub checkbook(targetWorkbook As CWorkbooks)
'Checking if passthrough is working
End Sub
Any help is appreciated, my types are aligned and everything so I'm not sure why this is occuring.
Thanks!
Sub Foo()
'single class object
Dim myClass1 As New clsClass
myClass1.StringName = "cls1"
Call Par(myClass1)
'or class array
Dim myClass2(1 To 5) As New clsClass
myClass2(1).StringName = "cls2"
Call Par(myClass2)
End Sub
Sub Par(ByRef lClass As Variant) 'same function call used for both
'Debug.Print lClass.StaffName & vbNewLine 'single class object
'Debug.Print lClass(1).StaffName & vbNewLine 'array version
End Sub
google brought me here for same problem but found the accepted answer lacking & didn't work at all in my case where Foo() was a module & Par() a worksheet, trying to pass class array.
I was able to duplicate your problem with the compiler. The following passes the compiler and runs. You declared TargetWorkbook as Variant, then set it to CWorkbooks - this works, but not when passed to the sub.
Sub main()
Dim TargetWorkbook As CWorkbooks
Set TargetWorkbook = New CWorkbooks
checkbook TargetWorkbook
End Sub
Sub checkbook(ByRef TargetWorkbook As CWorkbooks)
'Checking if passthrough is working
End Sub

Calling OnTime method in a Custom Class

I'm trying to use the .OnTime method in a class module, but can't figure out how to call a procedure in the class. All of the .OnTime examples I've seen refer to using the method from a standard code module rather than a custom class. Is there any way of calling a procedure in the class module rather than a standard code module?
#Alex P: Updated to include code. Here is the Class Module:
Option Explicit
Public Sub Test()
MsgBox "Success"
End Sub
Private Sub Class_Initialize()
Application.OnTime EarliestTime:=Now + TimeValue("00:00:03"), _
Procedure:="Test"
End Sub
And the Standard Module:
Option Explicit
Public Sub TestOnTime()
Dim OnTime As CCOnTime
Set OnTime = New CCOnTime
End Sub
I've also tried Procedure:="CClass.Test"
You can do it, but the call-back needs to be bounced back into the object from a Standard Module or a Worksheet Module or Thisworkbook.
Here is an example that pulses a value in a worksheet cell.
The timer is (almost) encapsulated in the cOnTime Class.
A cOnTime Object is instantiated in the host worksheet, whose code module can have a property to set the pulse time as well as the call-back routine.
If you protect the sheet, it will start pulsing and you can stop it by un-protecting the sheet.
If you navigate away from the host sheet, the timer is killed and if you navigate back it re-starts (as long as the sheet is protected).
Class cOnTime
Option Explicit
Const DEFPulseTime = "PulseTime"
Const DEFearliestTime As Long = 5
Const DEFlatestTime As Long = 15
Public WithEvents wb As Workbook
Public ws As Worksheet
Private DoWhen As String
Public mPulseTime As Long
Public mNextTime As Double
Property Let callBackDoWhen(cb As String)
DoWhen = "'" & wb.Name & "'!" & ws.CodeName & "." & cb 'e.g. 'wb Name.xlsm'!Sheet1.kickdog
End Property
Private Function PulseTime() As Long
On Error Resume Next
PulseTime = CallByName(ws, DEFPulseTime, VbGet)
If Err.number <> 0 Then
PulseTime = DEFearliestTime
End If
On Error GoTo 0
End Function
Property Get designMode() As Boolean
designMode = Not ws.ProtectContents
End Property
Public Sub kickDog()
Const myName As String = "kickDog"
Dim psMessage As String
If ws Is ActiveSheet And Not designMode Then
mNextTime = Now + TimeSerial(0, 0, mPulseTime)
On Error Resume Next
Application.OnTime mNextTime, DoWhen
On Error GoTo 0
End If
Exit Sub
End Sub
Public Sub killDog()
If ws Is Nothing Or mNextTime = 0 Then Exit Sub
On Error Resume Next
Application.OnTime mNextTime, DoWhen, , False
On Error GoTo 0
End Sub
Private Sub Class_Initialize()
Dim errorContext As String
On Error GoTo enableAndExit
Set wb = ActiveWorkbook
Set ws = ActiveSheet
On Error GoTo 0
callBackDoWhen = DEFDoWhen
callBackPulseTime = DEFPulseTime
mPulseTime = PulseTime
kickDog
Exit Sub
enableAndExit:
If Err <> 0 Then
If ws Is Nothing Then
errorContext = "ws"
ElseIf wb Is Nothing Then
errorContext = "wb"
End If
End If
End Sub
Private Sub Class_Terminate()
Const myName As String = "Class_Terminate"
On Error Resume Next
killDog
Set ws = Nothing
Set wb = Nothing
Exit Sub
End Sub
Private Sub wb_WindowActivate(ByVal Wn As Window)
wb_Open
End Sub
Private Sub wb_WindowDeactivate(ByVal Wn As Window)
killDog
End Sub
Private Sub wb_BeforeClose(Cancel As Boolean)
killDog
End Sub
Private Sub wb_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If SaveAsUI Then killDog
End Sub
In Worksheet Module
Option Explicit
Const cPulseTime As Long = 1
Dim mOnTime As cOnTime
Property Get PulseTime() As Long
PulseTime = cPulseTime
End Property
'****************************************
'Timer call-back for cOnTime
Public Sub kickDog()
' Code to execute on timer event
'******************************************
On Error Resume Next
Me.Cells(1, 1) = Not Me.Cells(1, 1)
On Error GoTo 0
'******************************************
Debug.Print "woof!!"
mOnTime.kickDog
End Sub
Private Sub Worksheet_Activate()
Me.Cells(1,1) = False
Set mOnTime = New cOnTime
End Sub
Private Sub Worksheet_Deactivate()
On Error Resume Next
Set mOnTime = Nothing
End Sub
You are asking for magic - VBA is a comprehensive tool but it is not magic.
The reason is that every Class module is simply a template which can be instantiated any number of times in the application code. Excel could not hope to correctly guess which particular instantiation of the Class module is the correct one on which to invoke the method. You are responsible for making this decision and managing the references to the appropriate Class instance.
Ah you say - But there is no private data/references being used by the method I want called. It is a static method. Well the answer to that is that VBA does not support static methods on Class modules, only on Standard modules. Any method that you wish to declare to the environment as being static is declared as being static by being included in a standard module.
So, place your call-back method in a Standard module, and declare a private member that holds a reference to the particular instance of the Class that you wish to handle the event.
One alternative route which isn't specified by others here but, I think, would theoretically work is:
Register VBA object as an active object with a specified GUID
Launch a PowerShell (or binary) daemon to connect to the object with specified GUID and call a specified method on that object every n seconds.
Pros:
Totally encapsulated
Cons:
Launches external process
Method likely has to be public (unless connection points can be abused)
Potentially crash-prone
I haven't implemented such a solution yet but intend to on stdCOM of stdVBA library if everything works out.

Re-Call a String/Object from previous Sub

Is it possible to recall a string or object that was stored in a previous sub?
The below code gives you an idea as to what I am trying to do.
Sub StoreUserData()
Dim StorName as Object
End
Sub WriteUserFile()
'Recall StorName here
End Sub
You need to make it into a field:
Dim StorName as Object
Sub StoreUserData()
'Do stuff with StoreName
End
Sub WriteUserFile()
'Recall StorName here
End Sub
If it is declared within a method, it is a local variable and not visible outside the method.
I suggest reading about Scope in Visual Basic.
Local variables are only accessible within their respective code block. To be able to access it you would have to widen its scope like this:
Class MyClass
Dim storName as Object
Sub StoreUserData()
storName = something
End Sub
Sub WriteUserFile()
' Use storName here
End Sub
End Class