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

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

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

How can I declare one variable with assigned value that can use for all module in vba?

My question is how my variable and assigned value can be use for all module, for example like:
this is declare global module:
sub global()
public A as integer
A=3
end sub
this is first module(for sheet1):
sub first()
if Sheet1.Cells(1, "A") > 100 then
Sheet3.Cells(A, "A") = Sheet1.Cells(1, "B").Value
A=A+1
eng sub
this is second module(for sheet2):
sub second()
if Sheet2.Cells(1, "A") > 100 then
Sheet3.Cells(A, "A") = Sheet2.Cells(1, "B").Value
A=A+1
end sub
*i had separate the modules file for every single sheet such as sheet1 with module1 and sheet2 with module2.
so I expect that when A sum inside the first or second module will return to global() A value.
example of my contain file
Based on comments to Vityata's answer, and edits to the Question:
Module1
Option Explicit
Public A as Long
'Long and Integer use the same amount of Memory in VBA,
' so there is no reason not to use Long
Sub getglobal()
A=3
End Sub
Module2
Option Explicit
Sub first()
If Sheet1.Cells(1, "A") > 100 Then
Sheet3.Cells(A, "A") = Sheet1.Cells(1, "B").Value
End If
Module1.A=Module1.A+1
End Sub
Module3
Option Explicit
Sub second()
If Sheet2.Cells(1, "A") > 100 Then
Sheet3.Cells(A, "A") = Sheet2.Cells(1, "B").Value
End If
Module1.A=Module1.A+1
End Sub
What you need is achieved with declaring the variable like this Public A As Integer outside a sub or a function, in a module.
Public A As Integer
Sub SetGlobal()
A = 3
End Sub
And then somewhere else:
Sub first()
A = A + 3
MsgBox A
End Sub
In general, you cannot name a Sub Global, because it is a name, used by the VBE.
If you want to have a minumum value of the public variable, then you may consider creating a class, which has a condition in the Get-property. If the minimum value is 3, then the class should look like this:
Option Explicit
Private m_lA As Long
Public Property Get A() As Long
A = m_lA
If A < 3 Then A = 3
End Property
Public Property Let A(ByVal lNewValue As Long)
m_lA = lNewValue
End Property
And you can call it from the modules like this:
Option Explicit
Public myVar As New publicA
Public Sub TestMe()
Debug.Print myVar.A
myVar.A = myVar.A * 30
Debug.Print myVar.A
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

Do I need to pass a worksheet as ByRef or ByVal?

I broke out some code from a larger block and need to pass a worksheet to it...
I'm not assigning any new value to the worksheet, but I am making changes to the Page Break settings for that sheet. Do I need to pass it as ByRef, or is ByVal good enough?
Private Sub SetPageBreaks(ByRef wsReport As Worksheet)
Dim ZoomNum As Integer
wsReport.Activate
ActiveWindow.View = xlPageBreakPreview
ActiveSheet.ResetAllPageBreaks
ZoomNum = 85
With ActiveSheet
Select Case wsReport.Name
Case "Compare"
Set .VPageBreaks(1).Location = Range("AI1")
ZoomNum = 70
Case "GM"
.VPageBreaks.Add before:=Range("X1")
Case "Drift"
.VPageBreaks.Add before:=Range("T1")
Case Else
.VPageBreaks.Add before:=Range("U1")
End Select
End With
ActiveWindow.View = xlNormalView
ActiveWindow.Zoom = ZoomNum
End Sub
Either will work, but for semantically correct code, prefer passing it by value (ByVal).
When you pass an object variable by value, you're passing a copy of the pointer to the object.
So what the procedure is working with is the same object (i.e. changed property values will be seen by the caller), except it's not allowed to Set the pointer to something else - well it can, but it'll do that on its own copy and so the caller won't be affected.
Public Sub DoSomething()
Dim target As Worksheet
Set target = ActiveSheet
Debug.Print ObjPtr(target)
DoSomethingElse target
Debug.Print ObjPtr(target)
End Sub
Private Sub DoSomethingElse(ByVal target As Worksheet)
Debug.Print ObjPtr(target)
Set target = Worksheets("Sheet12")
Debug.Print ObjPtr(target)
'in DoSomething, target still refers to the ActiveSheet
End Sub
On the other hand...
Public Sub DoSomething()
Dim target As Worksheet
Set target = ActiveSheet
Debug.Print ObjPtr(target)
DoSomethingElse target
Debug.Print ObjPtr(target)
End Sub
Private Sub DoSomethingElse(ByRef target As Worksheet)
Debug.Print ObjPtr(target)
Set target = Worksheets("Sheet12")
Debug.Print ObjPtr(target)
'in DoSomething, target now refers to Worksheets("Sheet12")
End Sub
In general, parameters should be passed by value. It's just an unfortunate language quirk that ByRef is the default (VB.NET fixed that).
The same is true for non-object variables:
Public Sub DoSomething()
Dim foo As Long
foo = 42
DoSomethingElse foo
End Sub
Private Sub DoSomethingElse(ByVal foo As Long)
foo = 12
'in DoSomething, foo is still 42
End Sub
And...
Public Sub DoSomething()
Dim foo As Long
foo = 42
DoSomethingElse foo
End Sub
Private Sub DoSomethingElse(ByRef foo As Long)
foo = 12
'in DoSomething, foo is now 12
End Sub
If a variable is passed by reference, but is never reassigned in the body of a procedure, then it can be passed by value.
If a variable is passed by reference, and reassigns it in the body of a procedure, then that procedure could likely be written as a Function, and actually return the modified value instead.
If a variable is passed by value, and is reassigned in the body of a procedure, then the caller isn't going to see the changes - which makes code suspicious; if a procedure needs to reassign a ByVal parameter value, the intent of the code becomes clearer if it defines its own local variable and assigns that instead of the ByVal parameter:
Public Sub DoSomething()
Dim foo As Long
foo = 42
DoSomethingElse foo
End Sub
Private Sub DoSomethingElse(ByVal foo As Long)
Dim bar As Long
bar = foo
'...
bar = 12
'...
End Sub
These are all actual code inspections in Rubberduck, as VBE add-in I'm heavily involved with, that can analyze your code and see these things:
Parameter is passed by value, but is assigned a new value/reference. Consider making a local copy instead if the caller isn't supposed to know the new value. If the caller should see the new value, the parameter should be passed ByRef instead, and you have a bug.
http://rubberduckvba.com/Inspections/Details/AssignedByValParameterInspection
A procedure that only has one parameter passed by reference that is assigned a new value/reference before the procedure exits, is using a ByRef parameter as a return value: consider making it a function instead.
http://rubberduckvba.com/Inspections/Details/ProcedureCanBeWrittenAsFunctionInspection
A parameter that is passed by reference and isn't assigned a new value/reference, could be passed by value instead.
http://rubberduckvba.com/Inspections/Details/ParameterCanBeByValInspection
That's the case of wsReport here:

Populate Various Comboboxes by Function Call

Morning !
I'm trying to make my code more generic by using Functions to populate various ComboBoxes, however, I'm with troubles to do this...
In UserForm:
Private Sub UserForm_Initialize()
Call Lista_Vendedores
ComboBox1.List = Lista_Vendedores
End Sub
In Module:
Public Function Lista_Vendedores() As Variant
Dim Lista As New Collection
Dim Cont_Vendedores As Integer
Cont_Vendedores = Plan1.Cells(1, 8).Value
Sheets("Plan1").Select
For i = 3 To Cont
Lista (Row.Cells(i, 8))
MsgBox Lista
Next i
Set Lista_Vendedores = Lista
Set Lista = Nothing
End Function
When I try to run the code, nothing happens, however, none error is shown.
Your code can be simplified a lot. It doesn't need to be in a function, just give it a range as per the example below:
Userform:
Private Sub UserForm_Initialize()
ComboBox1.List = ThisWorkbook.Worksheets("Sheet1").Range("A1:A8")
End Sub
Module:
Public Sub ShowUserform()
UserForm1.Show
End Sub
You are making lots of mistakes in your code, and while CallumDA33 solution is better, I think you should take a look at this:
First, you are not calling your function correctly. The sub should be:
Private Sub UserForm_Initialize()
ComboBox1.List = Lista_Vendedores
End Sub
The Call line is not necessary. (and the keyword Call itself is almost never required). I really don't get where your syntax is coming from.
In the Lista_Vendedores function, you are not using correctly the Lista Collection.
Cont is undefined. You should always use Option Explicit at the start of your modules.
You are not affecting any value to the collection.
You should also avoid using Select as much as possible.
The List property expect an variant array, not a collection, so the function should return a variant array, not an variant containing a single (empty) collection object, so I also changed the type for Lista.
Follows corrected but untested code:
Public Function Lista_Vendedores() As Variant()
Dim Lista As Variant()
Dim Cont_Vendedores As Integer
With Sheets("Plan1")
Cont_Vendedores = .Cells(1, 8).Value
Redim Lista(0 to Cont_Vendedores-3)
For i = 3 To Cont_Vendedores
Lista(i-3) = .Cells(i,8)
Next i
End With
Lista_Vendedores = Lista
End Function
Edit: corrected typo