Call Subroutine from a string value VBA - vba

trying to call this subroutine using a string. I have tried Application.Run like I have read online but that doesn't seem to be working.
The variable element will loop through and represents different state codes. So I have subs called "CA_Config", "GA_Config" "AZ_Config" etc.
Dim strSubToCall As String
strSubToCall = element & "_Config()"
Application.Run strSubToCall
State subs are very different therefore need to be different subroutines. The other subs and the main sub calling the other ones are all public.
Example for CA sub below
Public Sub CA_Config()
Dim rngLastHeader As Range
Dim intLastRow As Integer
Dim i As Integer
intLastRow = Sheet1.currWS.UsedRange.Rows.Count
Set rngLastHeader = Sheet1.currWS.Range("A1").End(xlToRight)
rngLastHeader.Offset(, 1).Value = "Use Tax Reversal Needed"
Sheet1.currWS.Range("X:X").EntireColumn.Copy
Sheet1.currWS.Range("Y:Y").PasteSpecial xlPasteFormats
Sheet1.currWS.Range("Y:Y").Columns.AutoFit
End Sub

Remove parenthesis and prepend your Sub name with module name. For instance, Application.Run "Module1.MySub".

Related

Calling a global/public variable in moduleB whose value was defined in moduleA

I wrote 4 macros to do things, but it requires 2 inputs from the user to make sure the right file is being used because some of the macros switch back and between 2 workbooks. I only had access to a few of the files, but I knew that eventually I would have access to the rest of the 35 files. If I didn't have the inputs, I would have to manually change the filename in the macro code, but I don't want to do that, so I used inputs. But now that I have all the files in the right format, I am trying to a separate macro that has a list of the other files in a separate workbook, and then opens those files and does the macros, but it would require the inputs a lot. So now, I'm trying to remove that need for the inputs. But I'm unfamiliar with public variables and somewhat familiar with the calling of other subroutines.
My setup is this:
option explicit
public current as string
Sub master_macro
dim i as integer
dim path as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("name.xlsx")
set sht = wb.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to 20
currun = sht.cells(i,1).value 'this takes the value from the separate workbooks that has the file names
full_currun = currun & ".xlsx"
with workbooks.open(path & full_currun)
.activate
call blanks
call lookup
call transfer
call combine
.save
.close
end with
next i
The last 2 macros switch between 2 sheets. So in those macros, the currun is generated the an inputbox, albeit a different name.
nam = inputbox("yadda yadda")
set wb = workbooks(nam & ".xlsx")
I'm trying to get the currun vaue that is defined in the master macro to macro3 and macro4.
You see the part where it says Sub master_macro? What you are doing there is declaring a procedure, which is a basically a general term to describe "a block of self-contained code that does something when it is run." Procedure declarations have three major components:
type - this is what you are doing with Sub; you are saying it is a subroutine, which is distinct from a function Function in that it does not return a value
name - this is the identifier you use to refer to the procedure elsewhere in your code. it is supposed to be descriptive since that enhances the readability. "master_macro" is not bad, but as a general rule you don't want to use underscores when naming procedures in VBA.
parameters - this is where you define the set of variable values that can be passed to the procedure when it is run. each parameter is separated by a comma and declared using the syntax [pass type] + [variable name] + [variable type]. [pass type] is either ByRef or ByVal; the basic distinction is that ByRef sends a direct reference to the variable, while ByVal sends a copy of the value.
The last part is what you are missing to solve this problem. Both macro3 and macro4 are declared (in module B) like master_macro is here. If they need to know what the currun value is then simply add (ByVal currun As String) to their declarations. When they are called from another procedure, as they are in master macro, they will expect to receive a string. Change the two lines in master macro from:
Call macro3
Call macro4
to
Call macro3(full_currun)
Call macro4(full_currun)
and macro3 and macro4 will have the value of full_currun stored in their own internal variable currun for use as they need.
Thanks guys. managed to get it to work. Here's the finished work below
sub master()
dim i as integer
dim path, currun, fullcurrun as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("Name.xlsx")
set sht = wh.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to ?
currun = sht.cells(i,1).value
fullcurrun = currun & ".xlsx"
workbooks.open(path & fullcurrun)
call blank(currun)
call lookup(currun)
call transfer(currun)
activeworkbook.save
activeworkbook.close
call transfer(currun)
next i
end sub
public sub blank/lookup/transfer(byval currun as string)
blah blah blah
end sub

Can I create a Jump table in VBA for Excel?

I wrote a simple translator / parser to process an EDI (830) document using multiple Select Case statements to determine the code to be executed. I’m opening a file in binary mode and splitting the document into individual lines, then each line is split into the various elements where the first element of every line has a unique segment identifier.
My code works perfectly as written. However, Select Case requires checking every Case until a match is found or the Case Else is executed. I’ve sequenced the Case statements in such a manner that the segments that appear most frequently (as in the case of loops), are placed first to minimize the number of "checks before code is actually executed.
Rather than using multiple Select Cases, I would prefer to determine an index for the segment identifier and simply call the appropriate routine using that index. I’ve used jump tables in C and Assembler and anticipated similar functionality may be possible in VBA.
You can do jump tables in VBA by using the Application.Run method to call the appropriate routine by name. The following code demonstrates how it works:
Public Sub JumpTableDemo()
Dim avarIdentifiers() As Variant
avarIdentifiers = Array("Segment1", "Segment2")
Dim varIdentifier As Variant
For Each varIdentifier In avarIdentifiers
Run "Do_" & varIdentifier
Next varIdentifier
End Sub
Public Sub Do_Segment1()
Debug.Print "Segment1"
End Sub
Public Sub Do_Segment2()
Debug.Print "Segment2"
End Sub
You can do this in Excel VBA, following the example below:
The example assumes you have split your EDI document into two columns, one with the 'processing instruction' and one with the data that instruction will process.
The jump table is to the right i.e. a distinct list of the 'processing instructions' plus a name of a Sub-routine to run for each instruction.
The code is:
Option Explicit
Sub JumpTable()
Dim wsf As WorksheetFunction
Dim ws As Worksheet
Dim rngData As Range '<-- data from your file
Dim rngCell As Range '<-- current "instruction"
Dim rngJump As Range '<-- table of values and sub to run for value
Dim strJumpSub As String
Dim strJumpData As String
Set wsf = Application.WorksheetFunction '<-- just a coding shortcut
Set ws = ThisWorkbook.Worksheets("Sheet1") '<-- change to your worksheet
Set rngData = ws.Range("A2:A17") '<-- change to your range
Set rngJump = ws.Range("E2:F4") '<-- change to your circumstances
For Each rngCell In rngData
strJumpSub = wsf.VLookup(rngCell.Value, rngJump, 2, False) '<-- lookup the sub
strJumpData = rngCell.Offset(0, 1).Value '<-- get the data
Application.Run strJumpSub, strJumpData '<-- call the sub with the data
Next rngCell
End Sub
Sub do_foo(strData As String)
Debug.Print strData
End Sub
Sub do_bar(strData As String)
Debug.Print strData
End Sub
Sub do_baz(strData As String)
Debug.Print strData
End Sub
Make sure that you have written a Sub for each entry in the jump table.

Value in cell as variable in select formula

I have programmed the following VBA code:
Sub test()
Dim Variable As Variant
Variable = "R2:R11"
Sheet1.Select
Range(Variable).Select
End Sub
This formula works perfectly. Now I want to replace the "R2:R11" part of the variable and instead referring to a cell (W12) in the spreadsheet. In this cell I have written "R2:R11" and I changed the formula to the following:
Variable = Sheet1.Range("W12")
which leads to the following code:
Sub test()
Dim Variable As Variant
Variable = Sheet1.Range("W12")
Sheet1.Select
Range(Variable).Select
End Sub
However, with this formula I now get the ERROR 1004.
Do you guys have any solution how I can make the variable referring to the cell in the spreadsheet and then use it in the select formula?
Thanks for any help :-)
You need to declare your variables properly and access the actual .Value property of the Range object.
Sub test()
Dim Variable As String
Variable = Sheet1.Range("W12").Value
Sheet1.Select
Range(Variable).Select
End Sub
Which can also be written as:
Sub test()
Sheet1.Activate
Range([W12]).Activate
End Sub
A note on the .Value property - if you omit this, the value will usually be assigned anyway because it's the default property of the range object. That being said there are scenarios where this won't be the case and therefore it's always best practice to explicitly state that you want the value.
you most probably typed "R2:R11" in W12 cell, i.e with double quotes too
in that cell you only have to type R1:R12
moreover you can simply code
Sub test()
Sheet1.Range(Sheet1.Range("W12")).Select
End Sub

How to change RefersTo for named range?

I have a class ValidationChanger, with a method changeNamedRangeAddress that should change the RefersTo address for a named range. However, my code is unexpectedly wrapping the new address in double quotes.
Class definition for ValidationChanger here:
'ValidationChanger
Sub changeNamedRangeAddress(bk As Workbook, rangeName As String, newAddress As String)
bk.Names(rangeName).RefersTo = newAddress
End Sub
I test it on a range named TestRange, which refers to an address in sheet Instructions: Instructions!$A$133:$A$138. My test should change the address to Instructions!$A$133:$A$139 with the following:
Sub testValidationChanger()
Dim vc As New ValidationChanger
Dim bk As Workbook
Set bk = Workbooks("test.xlsm")
Debug.Print bk.Names("TestRange").RefersTo
vc.changeNamedRangeAddress bk, "TestRange", "Instructions!$A$133:$A$139"
Debug.Print bk.Names("TestRange").RefersTo
End Sub
The output is:
=Instructions!$A$133:$A$138
="Instructions!$A$133:$A$139"
Any idea why the new address is wrapped in double quotes (which makes it function as a text string instead of an address)?
Pass the new range as a range object, and not as a string:
vc.changeNamedRangeAddress bk, "TestRange",[Instructions!$A$133:$A$139]
The reason you got a text string is that you did not preface the text string with an = sign (so that Excel would know it was supposed to be a formula)
If you use a named range instead of the addresses then you can alter that named range to adapt to whatever you want.
'this line creates the name that you can use in formulas in
'much the same way as "Instructions!$A$133:$A$139"
thisworkbook.Names.Add Name:="examplerange", RefersTo:=Range("A1:A5")
'this line changes what it's pointing to
thisworkbook.Names("exampleRange").RefersTo = Range("A1:A10")

How do I refer to a controls object, on a worksheet, using a variable name?

I have added a ListBox to a SHEET (not to a "UserForm")
I did this using the mouse.
I clicked the little Hammer and Wrench icon.
This ListBox seems to be easily referenced using code such as this:
ListBox1.Clear
or
ListBox1.AddItem("An option")
However, I have three of these ListBoxes (named, conveniently, ListBox1, ListBox2, and ListBox3) and I want to write a function to populate them with array data, like this:
Call populate_listbox(ListBox2, designAreaArray)
Where the first argument is the listbox name, the 2nd is the data.
But I do not know how to send "ListBox2" correctly, or refer to it correctly within the function.
For example:
Dim controlName as string
controlName = "ListBox1"
doesn't work, even if I define the function as follows:
Sub populate_listbox(LB As ListBox, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.AddItem (dataArray(index, i))
Next i
End Sub
Clearly it results in a mis-matched data type error. I've tried defining "controlName" as a ListBox, but that didn't work either...
Though perhaps it is my reference to the listBox that is incorrect. I've seen SO MANY ways to refer to a control object...
MSForms.ListBox.
ME.ListBox
Forms.Controls.
Worksheet.Shapes.
The list goes on an on, and nothing has worked for me.
Try this:
Dim cMyListbox As MSForms.ListBox
Set cMyListbox = Sheet1.ListBox1 '// OR Worksheets("YourSheetName").Listbox1
cMyListbox.AddItem("An option")
Also you can populate a listbox without having to loop through the array, try this:
Dim cMyListbox As MSForms.ListBox
Dim vArray As Variant
Set cMyListbox = Sheet1.ListBox1
vArray = Range("A1:A6").Value
cMyListbox.List = vArray
Change the sub signature to match this:
Sub populate_listbox(LB As MSForms.ListBox, dataArray As Variant)
Now you can pass it like you were trying to originally.
NOTE: This only works if you used the "ActiveX" version of the listbox. I'm assuming you are because you are able to call ListBox1 straight from a module.
PS: The ActiveX controls are members off of the parent sheet object. So if you have the listbox1 on sheet1, you can also call it like Sheet1.ListBox1 so you don't get confused if you end up with multiple sheets with multiple listboxes. Also, you may want to change the name just to make it easier on yourself.
Dim controlName As OLEObject
Set controlName = Sheet1.OLEObjects("ListBox1")
Call populate_listbox(controlName, designAreaArray)
Sub populate_listbox(LB As OLEObject, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.Object.AddItem (dataArray(Index, i))
Next i
End Sub
To access the state of a checkbox Active-X control on Sheet1:
Dim checkBox1 As Object
Set checkBox1 = Sheet1.OLEObjects("CheckBox1").Object
MsgBox checkBox1.Value