Can procedure definitions be nested in Excel VBA? - excel

Is it possible to nest procedure definitions in VBA so that the inner Sub has access to the outer Subs variables?
Something like...
Sub Outer()
Dim dataSheet As Worksheet
Dim sheetName As Variant
Dim sel As Range
Dim destCol As Variant
Dim defCol As String
Dim outerLoop As Boolean
Sub Inner()
' Can I do this and get access to the outer variables in here?
' So that I can pass them to the Sub call in here and have that
' Sub call access the outer variables?
dataSheet.Activate
Set sel = dataSheet.ListObjects("ReqMarkerTable").ListColumns("Quantity").DataBodyRange
destCol = GetInputBoxVal("Enter destination column:", "Column?", defCol)
If destCol <> False Then
Call DuplicateValsByQty(sheetName, destCol, sel)
outerLoop = False
End If
End Sub
End Sub
Sub DuplicateValsByQty(sheetName As Variant, destCol As Variant, sel As Range)
' I want this Sub's call within the Inner Sub to have access to the outer variables.
End Sub
I want to avoid using global variables if possible.
Ultimately, I want to have several of these "Inner" Subs that process the same data in different ways.
Or is there some way that data in one Sub can be accessed from another? Something like dot notation in JS?
Perhaps I am looking at this all wrong?
Any help or direction provided would be greatly appreciated.

Related

Public Property passing to other sheet modules

I have a VBA script that uses a Private Sub Open_Workbook() to initialize some arrays from one of the excel sheets to be used as lookup tables later. The Private Sub Open_Worbook() is placed in the ThisWorkbook module and it appears to do it's job when the excel workbook is open.
The arrays in the Open_Workbook() sub are Dim as Variants but I have not been able to "pass" them along to Sheet1 for example to be used in the Sub for that sheet. I have tried using an accessor similar to what was suggested at the following link:
Create and assign variables on Workbook_open, pass it to Worksheet_change
Here is my code following that suggestion.
Following code is in the ThisWorkbook module of the excel workbook:
Option Explicit
Private Test_Array() As Variant
Private Sub Workbook_Open()
' Code here to redim preserve Test_array and set elements of the array and other code that
' Added snippits of code to
Dim Test_Array() As Variant
For i = 1 To UBound(TestRange,2)
ReDim Preserve Test_Array(i - 1)
Test_Array(i-1) = TestRange(1,i)
Next i
End Sub
Public Property Get TestArray() As Variant
TestArray = Test_Array()
End Property
I was hoping that I would be able to use Thisworkbook.TestArray in Sheet1 to do some calculations on it but when I have a Msgbox ThisWorkbook.TestArray(0), I am getting a Subscript out of range error. I debugged the code and it appears that the Public Property Get TestArray(), Test_Array() is empty. What am I doing wrong? Can I not use Variant with Public Get Property?
I did confirm that the Test_Array in the Workbook_Open() Sub is indeed populated with the expected elements.
Edited: Added code for populating Test_Array
You have a Dim Test_Array() As Variant on module level and in the Private Sub Workbook_Open(). So you never populate Test_Array on the module level.
The following code is working for me
Workbook class module:
Option Explicit
Private Test_Array() As Variant
Private Sub Workbook_Open()
' Code here to redim preserve Test_array and set elements of the array and other code that
' Added snippits of code to
'Dim Test_Array() As Variant
Dim testrange
Dim i As Long
testrange = Sheet1.Range("A1:A4").Value
For i = 1 To UBound(testrange, 2)
ReDim Preserve Test_Array(i - 1)
Test_Array(i - 1) = testrange(1, i)
Next i
End Sub
Public Property Get TestArray() As Variant
TestArray = Test_Array()
End Property
Normal module
Option Explicit
Sub testit()
Debug.Print ThisWorkbook.TestArray(0)
End Sub
Reading on Lifetime of variables

ByRef Type Mismatch when calling another sub

I am trying to have a sub in VBA to call for another sub by passing the i value using the for loop. The problem is when u run the forLoop() sub, there is an error ByRef Type Mismatch. I am just trying out this example method to see if it works so that i can put into my bigger macro.
I tried to add the ByVal method but the error still remains. Someone please help me.
The forLoop sub
Public Sub forLoop()
For i = 1 To 3
Call Fetch_data(i)
Next i
End Sub
The Fetch_data() sub
Sub Fetch_data(num As Integer)
Dim data As String
Dim myCellValue As range
Dim myCellValue1 As range
Dim sht As String
Set sht = "mySheet" & num
Set myCellValue = sht.range("J6")
Set myCellValue1 = sht.range("J8")
myCellValue1.Value = myCellValue.Value
End Sub
The issue is that
Sub Fetch_data(num As Integer)
is the same as
Sub Fetch_data(ByRef num As Integer)
so Call Fetch_data(i) gives i by reference. But since in forLoop() the variable i is not declared it is Variant by default and not Integer therefore the type mismatch. Make sure you use Option Explicit and declare all your variables properly:
Public Sub forLoop()
Dim i As Integer
For i = 1 To 3
Fetch_data i
Next i
End Sub
Also you can make the parameter ByVal as there is no need to have it ByRef (otherwise your sub Fetch_data could mess up the loops counter in forLoop:
Sub Fetch_data(ByVal num As Integer)
Dim data As String
Dim myCellValue As range
Dim myCellValue1 As range
Dim sht As Worksheet
Set sht = ThisWorkbook.Worksheets("mySheet" & num)
Set myCellValue = sht.range("J6")
Set myCellValue1 = sht.range("J8")
myCellValue1.Value = myCellValue.Value
End Sub

VBA EXCEL: Apply sub to another worksheet from module

Trying to get a sub from a module to run within another worksheet. In a sense to stop using redundant ranges and keep it streamlined.
i.e.
-Module object-
Public sub method1()
{
Range("B4:B23") = ""
Range("C4:C23") = ""
'Empties these ranges...
}
-worksheet(s)-
sub project)
{
with sheet1 (or on any sheet 2,3,4,5... etc.)
Call module1.method1
'but this method only works on the module object, not in the context of the 'specified worksheet where it is needed
End with
Ideally to clear the data in ranges by using method1 in ANY worksheet. Every reference I tried just runs the module1 method without any effect or makes a useless reference to the method or worksheet. Just trying to save on code space by not writing direct references to every sheet which is formatted identical.
You can do this with a sub (ClearCells) that accepts a variable number of arguments via the ParamArray keyword. Then you can simply call the ClearCells sub and pass it the worksheet objects you want to clear the same ranges in, as in the DoClear sub. You can add more ranges as needed to the Union function in the GetRanges function.
Sub DoClear()
ClearCells Sheet1, Sheet3
End Sub
Sub ClearCells(ParamArray wkshts() As Variant)
Dim vWs As Variant
Dim ws As Worksheet
For Each vWs In wkshts
Set ws = vWs
GetRanges(ws).Clear
Next vWs
End Sub
Function GetRanges(ws As Worksheet) As Range
With ws
Set GetRanges = Union(.Range("B4:B23"), _
.Range("C4:C23"))
End With
End Function
Or assuming you are calling the method from the sheet you want to clear, you can just use ActiveSheet:
Public Sub Method1()
ActiveSheet.Range("B4:B23").Clear
ActiveSheet.Range("C4:C23").Clear
End Sub

Passing Variant ByRef in VBA Excel 2007

I have a public Variant variables declared in a UserForm called MainForm
Public increaseArray As Variant
Public countryArray As Variant
Then in sub on button click for the MainForm:
Sub testButton_Click()
Dim country As Variant
Set countryArray = Module1.callSomeFunctionThatReturnsVariant(1)
Set increaseArray = Module1.callSomeFunctionThatReturnsVariant(2)
For Each country In countryArray
Call Module1.createPage(country)
Next country
End Sub
In Module1 I have:
Function callSomeFunctionThatReturnsVariant(ByVal testInt As Integer) As Variant
.... do something when testInt = 1
.... do something when testInt = 2
callSomeFunctionThatReturnsVariant = someVariant
End Function
Public Sub createPage(ByVal country As String)
Dim testInt As Integer
... do something
testInt=insertSection(country, MainForm.increaseArray)
End Sub
Function insertSection(ByVal country As String, arr as Variant) As Integer
Dim arrCountry As Variant
For Each arrCountry In arr
If country = "France" Then
...do something
insertSection = 1
Exit Function
End If
Next arrCountry
insertSection = 2
End Function
I get ByRef argument type mismatch error when passing MainForm.increaseArray to insertSection() function. I've tried using Function insertSection(ByVal country As String, ByVal arr as Variant) As Integer but I get same error.
If I try to define a Variant variable in createPage sub Dim testArray As Variant and get the increaseArray from its getter function Set testArray = MainForm.getterForIncreaseArray I get type mismatch error...
If I pass getter function directly to caller of insertSection function I get ByRef argument type mismatch...
Please help :)
this simple code works fine.
declaring public array in userform not allowed (so using variant as disguise was good idea).
But then, Functions dont want to accept passing it as argument as a legit array, so i used a temporary 'legit' array.
on UserForm1 :
Option Explicit
Public a As Variant 'i would usually declare it like this : Public a() as variant, but public arrays not allowed in userforms (throws error)
'Private a() as variant , would not throw error (inside userform)
Private Sub UserForm_Initialize()
Dim i&
ReDim a(1 To 2) 'absolutely needed, it shows a is actually an array type
a(1) = 1
a(2) = 2
End Sub
Private Sub UserForm_Terminate()
Erase a
End Sub
in a module :
Option Explicit
Sub test()
Load UserForm1
Dim b&
Call get_value(1, UserForm1.a, b)
Unload UserForm1
MsgBox b
End Sub
Sub get_value(ByVal i&, ByRef arr As Variant, ByRef answer As Long) ' function won't let it through, i used a sub with aditionnal variable as Byref.
answer = arr(i)
End Sub
Launch it by calling TEST.
Note : i didn't succeed in passing argument in a Function, so did it in a SUB by adding an argument called Answer, wich is Byref.
Note2 : i looked back at my older code, and it would seem that you can pass a byref Array (disguised as variant) in a function , but maybe because this one is declared not with () or whatever, it don't want to work through a function.
Note 3 : after thurther digging into it, i found a solution using function, and as i thought, the array-declaring was the troublemaker :
'in a module (use the same userform as before)
Sub test()
Load UserForm1
Dim b&
Dim i& 'counter
Dim Temp_Array() As Long 'as variant works too, but i filled it with numbers so as long is ok too
ReDim Temp_Array(LBound(UserForm1.a) To UBound(UserForm1.a))
'Temp_Array = UserForm1.a 'damn, i first thought this would work, in the same way you can fill a listbox in one simple line (wich would be a 3rd solution passing an array from the userform to a module)
For i = LBound(UserForm1.a) To UBound(UserForm1.a)
Temp_Array(i) = UserForm1.a(i)
Next i
b = get_value(1, Temp_Array)
Erase Temp_Array
Unload UserForm1
MsgBox b
End Sub
Function get_value(ByVal i&, ByRef arr As Variant) As Long
get_value = arr(i)
End Function
As per findwindow's comment. You can't make use of things in module 1 from within the form as it's beyond its scope. Instead, try putting all the code from module1 into a new class module. Instantiate an instance of that from within your form and it should work ok.

How to assign a name to an Excel cell using VBA?

I need to assign a unique name to a cell which calls a particular user defined function.
I tried
Dim r As Range
set r = Application.Caller
r.Name = "Unique"
The following code sets cell A1 to have the name 'MyUniqueName':
Private Sub NameCell()
Dim rng As Range
Set rng = Range("A1")
rng.Name = "MyUniqueName"
End Sub
Does that help?
EDIT
I am not sure how to achieve what you need in a simple way, elegant way. I did manage this hack - see if this helps but you'd most likely want to augment my solution.
Suppose I have the following user defined function in VBA that I reference in a worksheet:
Public Function MyCustomCalc(Input1 As Integer, Input2 As Integer, Input3 As Integer) As Integer
MyCustomCalc = (Input1 + Input2) - Input3
End Function
Each time I call this function I want the cell that called that function to be assigned a name. To achieve this, if you go to 'ThisWorkbook' in your VBA project and select the 'SheetChange' event then you can add the following:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Left$(Target.Formula, 13) = "=MyCustomCalc" Then
Target.Name = "MyUniqueName"
End If
End Sub
In short, this code checks to see if the calling range is using the user defined function and then assigns the range a name (MyUniqueName) in this instance.
As I say, the above isn't great but it may give you a start. I couldn't find a way to embed code into the user defined function and set the range name directly e.g. using Application.Caller.Address or Application.Caller.Cells(1,1) etc. I am certain there is a way but I'm afraid I am a shade rusty on VBA...
I used this sub to work its way across the top row of a worksheet and if there is a value in the top row it sets that value as the name of that cell. It is VBA based so somewhat crude and simple, but it does the job!!
Private Sub SortForContactsOutlookImport()
Dim ThisCell As Object
Dim NextCell As Object
Dim RangeName As String
Set ThisCell = ActiveCell
Set NextCell = ThisCell.Offset(0, 1)
Do
If ThisCell.Value <> "" Then
RangeName = ThisCell.Value
ActiveWorkbook.Names.Add Name:=RangeName, RefersTo:=ThisCell
Set ThisCell = NextCell
Set NextCell = ThisCell.Offset(0, 1)
End If
Loop Until ThisCell.Value = "Web Page"
End Sub
I use this sub, without formal error handling:
Sub NameAdd()
Dim rng As Range
Dim nameString, rangeString, sheetString As String
On Error Resume Next
rangeString = "A5:B8"
nameString = "My_Name"
sheetString = "Sheet1"
Set rng = Worksheets(sheetString).Range(rangeString)
ThisWorkbook.Names.Add name:=nameString, RefersTo:=rng
End Sub
To Delete a Name:
Sub NameDelete()
Dim nm As name
For Each nm In ActiveWorkbook.Names
If nm.name = "My_Name" Then nm.Delete
Next
End Sub

Resources