Excel vba nested inner subs like in python, java - vba

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()

Related

How to end one procedure (Sub, Function etc) from another in VBA?

I want to end a main sub from another sub or function.
Here is an example code to illustrate what I need to do:
Sub main()
Call endMainSub
'do other stuff
End Sub
Sub endMainSub()
'here I need a code to break main Sub
End Sub
From endMainSub, I would like to terminate main sub before "do other stuff".
You should use a function for that.
Sub main()
If Not endMainSub Then
'do other stuff
End If
End Sub
Private Function endMainSub() As Boolean
Dim Fun As Boolean ' function return value
' break if A3 = "No" OR C3 > 10
Fun = (Cells(3, "A").Value = "No")
If Not Fun Then
Fun = (Cells(3, "C").Value > 10)
End If
endMainSub = Fun
End Function
Make the function Private if you aren't going to call it from another module. Remove the Private to make it Public by default.
End Finishes all code execution. Alternatively, if you need more control, have a global flag (a boolean variable), based on which execution will continue or not.

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

VBA value persistence

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

VBA variables defined one sub and called in another in multiple subs

I want to call a sub that I've written in another sub, and add a couple new things to it, but use the variables that I've defined in the first sub don't show up in the second, even though the first macro is called within the sub.
For example, I've defined the variables as Public outside of the subs, but the values I define in the first sub, and then call in second sub gets lost. In the code below, running the macro "test" works, but "test1" gives me a "Run-time error '1004'".
Public row1 As Integer
Public col1 As Integer
Sub test()
row1 = 2
col1 = 2
ActiveSheet.Cells(row1, col1).Select
End Sub
Sub test1()
Call test
ActiveSheet.Cells(row1, col1).Resize(6, 5).Select
End Sub
Any guidance on how to get row1 and col1 to work in the test1 sub would be great. Is there a better way to achieve what I'm trying to do?
An alternative option is to pass variables from sub to use a Public Function like this:
Sub MainSubHere()
'Some random code
Dim AddMe1 As Long, AddMe2 As Long, MySum As Long
AddMe1 = 2
AddMe2 = 2
MySum = AddMeSub(AddMe1, AddMe2)
Msgbox MySum
End Sub
Public Function AddMeSub(AddMe1 As Long, AddMe2 As Long) As Long
AddMeSub = AddMe1 + AddMe2
End Sub
You can try, but there's other stuff to improve here, such as using Select rathering then just going to the action statement. I left out that sub out since it served no purpose.
Dim row1 As Integer, col1 As Integer
Private Sub DefineVars()
Set row1 = 2
Set col1 = 2
End Sub
Sub test()
Call DefineVars
ActiveSheet.Cells(row1, col1).Resize(6, 5).Select
End Sub

VBA: evaluation order

when VBA executes this line:
GetClass1().Test(GetParam())
the GetParam function is evaluated before the GetClass1() call.
What is a good way to change this behaviour?
the only thing I came up with is this workaround:
With GetClass1
.Test(GetParam())
End With
here's the full example code, so that you can easily test it:
Class1
Option Explicit
Public Function Test(ByVal sText As String) As String
Debug.Print "Class1.Text: " & sText
Test = "Class1.Text: " & sText
End Function
Module1
Option Explicit
Private Function GetClass1() As Class1
Set GetClass1 = New Class1
Debug.Print "GetClass1()"
End Function
Private Function GetParam() As String
GetParam = "Param"
Debug.Print "GetParam()"
End Function
Private Sub Test()
Debug.Print "Test=" + GetClass1().Test(GetParam())
With GetClass1
Debug.Print "TestWith=" + .Test(GetParam())
End With
End Sub
Output when you run Test()
GetParam()
GetClass1()
Class1.Text: Param
Test=Class1.Text: Param
GetClass1()
GetParam()
Class1.Text: Param
TestWith=Class1.Text: Param
The evaluation order here is ok i think. The calling order of nested functions is from the inner most one to the outer most one which can't be done differently because outer most function needs to know its arguments and this arguments are evalueted only after the inner function was executed.
In your code (the first way) the object of type Class1 is created after the function GetParam() was called and this is because the object is created at the moment when function GetClass1() is called. In the second way with With GetClass1 the object is created immedialtelly after With and the call stack looks differently indeed.
What you can do is to create another class say 'Wrap' and this class will be responsible for creation of instance of type Class1.
E.g. like this:
' Class module Wrap
Private m_class1 As Class1
Public Function GetClass1() As Class1
Set GetClass1 = m_class1
Debug.Print "GetClass1()"
End Function
Private Sub Class_Initialize()
Set m_class1 = New Class1
End Sub
' Module code
Private Function GetParam() As String
GetParam = "Param"
Debug.Print "GetParam()"
End Function
Private Sub Test()
Dim wp As Wrap
Set wp = New Wrap
Debug.Print "Test=" + wp.GetClass1().Test(GetParam())
Debug.Print "---------------------------------------"
With New Wrap
Debug.Print "TestWith=" + .GetClass1.Test(GetParam())
End With
End Sub
Here the instance of class Class1 is created exactly at the moment you call New for class Wrap. So Set wp = New Wrap executes and creates the instance and the same way works With New Wrap, it executes and creates the instance as well.
But do not do it like this:
Dim wp As New Wrap
... then you will have the same behaviour like you had when the function GetClass1() was part of the Module1, which is: 'the instance is not created until it is needed' so you do not have the control of the moment of creation. HTH