Excel macro - divide procedure into more subs - excel

My procedure has become too large and won't run anymore. I've redesigned my code in my mind, but I need to get my current code up and running before I start editing. I've looked online and they say you need to split your procedure into subs and call them, but none really explain or show how you do this.
Thanks!

Sub Sub1()
' Code...
End Sub
Sub Sub2()
' Code...
End Sub
Sub Main()
Sub1
Sub2
End Sub

The first step is to take parts of code that are duplicated, and created one subroutine or function that you put that part into. Then you call the subroutine or function instead of duplicating the code everytime you need it.

Sub MacroName
Call ProcedureName1
Call ProcedureName2
etc...
End Sub
Sub ProcedureName1
'insert your vba code here
End Sub
The Call statement pulled each of the subs as long as I called the correct named sub.

Related

How can I run different subs one by one?

I'm new in VBA
I wrote a code like this:
sub a1
some codes
end sub
sub a2
some codes
end sub
when I run my code the only code that be executed is sub a1!
how can I run other subs?
If you have 2 procedures
Sub FirstProcedure()
'Some code
End Sub
Sub SecondProcedure()
'Some code
End Sub
and you want to run both of them sequencially, you just need to call them
Sub RunBoth()
FirstProcedure
SecondProcedure
'Some more code
End Sub
If you now run RunBoth it will call the FirstProcedure and after that has finished it will call SecondProcedure
If you just want to call one of the procedures from the VBA editor, place your cursor in one of the procedures and press Run at the toolbar or F5 on the keyboard.
Alternative to #PEH 's approach via Application.Run
This approach demonstrate possible usage via Application.Run. Similar to OP, I confined myself to procedure calls without passing arguments here.
A) Numbered procedure calls
If you'd dispose of numbered procedures with identical name prefix (e.g. "Proc1", "Proc2"),
you may code as follows:
Sub ExampleCall()
runNumberedProcedures "Proc", 2
End Sub
Sub runNumberedProcedures(ByVal ProcName As String, ByVal ProcNumber As Long)
Dim i As Long
For i = 1 To ProcNumber
Application.Run "Proc" & i
Next i
End Sub
Undocumented Caveat
Note that Run needs an additional module prefix for procedure names ranging from A to XFD (e.g. in the case of Module.a1 or Module1.a2),
as apparently VBA tries to avoid internal conflicts with Excel column names. Therefore a numbered procedure starting with "XFE" or "Proc" needn't be prefixed expressly.
Side note: Personally I'd prefer more meaningful naming conventions than a1,a2 or Proc1,... apart from testings.
B) Procedure calls via list of procedure names
If you want to run any procedure names in a given order, you might call a sub and pass a procedure list as argument (here: "Proc1,Proc2,Module1.a1"):
Sub ExampleCall2()
'[1]run listed procedures
runListedProcedures "Proc1,Proc2,Module1.a1"
'do other stuff
'...
'[2]run procedure a2 later
Run "Module1.a2" ' note module prefix for proc names from A to XFD !
End Sub
Sub runListedProcedures(ByVal ProcList As String)
Const Delim As String = ","
Dim procedures As Variant
procedures = Split(ProcList, Delim)
Dim i As Long
For i = LBound(procedures) To UBound(procedures)
Application.Run procedures(i)
Next i
End Sub

Call to a method or determine if method is working/exists?

Is there a way that vba can determine if a method is present/available?I have two different methods that will essentially be doing the same thing, but in different ways: one being an SQL server and the other being an excel file. I want to be able to first call the SQL method, and if whatever reason that SQL method is unavailable, it will then call the excel method. But what would be the line of code in the if statement?
I know that you "Call" to methods, but what essentially is the line of code that would be:
If SQLMethod() ... Then
Call SQLMethod()
Else
Call ExcelMethod()
End If
What would the code be at "..." to check this?
Any help would be appreciated!
You cannot have a reference to a routine that doesn't exist; the code won't compile, and you'll receive a "Complie error: Sub or Function not defined".
But, you can do this:
Sub Main()
On Error Resume Next
Application.Run "Test1"
If Err <> 0 Then Application.Run "Test2"
End Sub
Sub Test1()
Debug.Print "Test1()"
End Sub
Sub Test2()
Debug.Print "Test2()"
End Sub

Is there a way to tell if the Sub currently running is the parent or a called child

I have routines in VBA that can run standalone or can be called by another routine. When called as a subroutine (child) or as a primary routine (parent) there are operations that I may or may not want to execute. Is there a built-in function in VBA that I can call that will tell me if my executing code is a parent or child?
I have created global variables to test for parent/child but I would like to have a more elegant solution.
I think a good way would be to have a procedure for the action itself that has a switch (parameter) and a procedure that calls it.
Private Sub MyProcedure(Optional ByVal IsChild As Boolean = True) 'set default here
If IsChild Then
'child
Else
'parent
End If
End Sub
Now you can have a procedure to call it
Public Sub ParentCallMyProcedure()
MyProcedure IsChild:=False
End Sub
Public Sub ChildCallMyProcedure()
MyProcedure IsChild:=True
'which would be the same as
MyProcedure
End Sub
Eg if you want to call MyProcedure from a button then use
Public Sub Button1_Click()
MyProcedure IsChild:=False
End Sub
In all other procedures just use MyProcedure and IsChild is default True.
At least this is more elegant than a public/global variable.
In .Net getting info for a method, which called a method is called Reflection. It is quite straight-forward in C# - How can I find the method that called the current method?. VBA does not support it, but you could run around it, and log somewhere data about it - through a variable or logging to a worksheet or database.
There is a way to do it, if you stop the code in the middle of the called sub/function and call the Call Stack diaglog box. E.g., imagine the following seqeuence:
Sub TestMe()
Testme2
End Sub
Sub Testme2()
Stop
End Sub
If you run the code and press Ctrl+L once you are on the Stop you would get this:
If you only run TestMe2 and press Ctrl+L, you would get it correspondingly:
While the following is not really a solution it may work depending on your setup:
This is my Occam's Razor solution that I have used in the past.
Public ChildCount as Integer
Sub EveryProc()
ChildCount = ChildCount + 1
... rest of code...
ChildCount = ChildCount - 1
End Sub
This allows me to test how far I am into subroutines as ChildCount will be 1 for the parent and >1 for the children. I think the first time into the VBA, ChildCount will be zero so you need to increment and decrement the variable at the beginning and end of every sub.
I am spoiled in SAP...

Difference between Calling a Sub and Application.Run

In my business, we have a few teams that work on very simple macros. I'm trying to make them all readable to each other and in somewhat of a similar format, so new joiners can start working on the data.
I mention simple macros, because no one will be using Subs with arguments - most are derived from Macro Recorder anyway
Half of the teams use:
Sub button()
Call sub1()
Call sub2()
Call sub3()
Call sub4()
End Sub
And the other half use
Sub button()
Application.Run("sub1")
Application.Run("sub2")
Application.Run("sub3")
Application.Run("sub4")
End Sub
I understand that if your sub has no arguments, then Application.Run has a use - but being as there's barely any notation on it - is there a reason people still use Application.Run("")?
Can every use of it be beaten in speed and process by Call?
You can pass parameters through application.run as well. I use it when I am looping through macros. in your above example instead of having to write this:
Sub button()
Call sub1()
Call sub2()
Call sub3()
Call sub4()
End Sub
you could write this:
for i = 1 to 4
application.run("sub" & i)
next i
if the subs took in a str parameter you could do this:
for i = 1 to 4
application.run("sub" & i, strVariable)
next i
I use Application.Run if I’m trying to run a sub that is private in another module. If I have a some kind of template where I want to hide the macros from the users I will declare it as private so they can’t run/see the macro from there macros dialog box.
In module1 I have
Private Sub priv()
MsgBox “Private”
End Suv
In module2 the below will give you a Sub or Function not defined error.
Sub callPriv()
Call priv()
End Sub
But in module2 this will run and display the message box
Sub callPriv()
Application.Run “priv”
End Sub
It’s also useful to use Application.Run if you are calling a sub in your sheet or thisWorkbook modules.
I am posting some of this as an answer, because I cannot comment.
# Badja : You said ….” I understand that if your sub has no arguments, then Application.Run has a use – …” I am not sure if you are possibly implying that Application Run can have no arguments? - That is not the case. In principal , you can use arguments with Application.Run just as you can with Call. The syntax and general working of it can be a bit more difficult to understand than with Call. I agree with you , that documentation on Application.Run is very sparse.
#Hasib_Ibradzic : I was not aware that you can use the Call function with strings. ??
As far as I know, it is one of the advantages of Application.Run over using Call, that the macro name part of the argument is taken as a sting, so that you can build that string up with variables. So you are not limited to hard coding, as I think is the case with using Call
#Dude_Scott : In your example, I think it is advisable to suggest/ note that including the module code name could be advisable. Without this, you could experience problems if you had Sub routines with the same name in different modules.
In your example, this could be advisable to do
Sub callPriv()
Application.Run "Module1.priv"
End Sub
_._____________
Some other things that might be useful to Note:
If you have a macro in another module which is Public, then you can Call it from another module
For example. In my ( German ) Excel I have in my first normal module , code name Modul1 , ( In English Excel, I think that would typically be by default Module1 ), this
Public Sub PbicModule1() '
MsgBox "Pubic Modul1"
End Sub
And in my first worksheet class code module, code name Tabelle1 ( In English Excel I think that would typically be by default Sheet1 ) , I have this
Public Sub PbicTabelle1() '
MsgBox "Pubic Tabelle1"
End Sub
And in my ThisWorkbook class code , code name DieseArbeitsmappe ( In English Excel , I think that would typically be by default ThisWorkbook ), I have this
Public Sub PbicThisWorkbook() '
MsgBox "Pubic ThisWorkbook"
End Sub
The following macro will work when it is in any module
Private Sub CallMePubics() ' https://stackoverflow.com/questions/55266228/difference-between-calling-a-sub-and-application-run
Call Modul1.PbicModule1
Call Tabelle1.PbicTabelle1
Call DieseArbeitsmappe.PbicThisWorkbook
End Sub
That last macro would fail if the called macros were Private.
This following macro equivalent using Application.Run , would work regardless of whether the called macros were Public or Private
Private Sub AppRunMePubics()
Application.Run Macro:="Modul1.PbicModule1"
Application.Run Macro:="Tabelle1.PbicTabelle1"
Application.Run Macro:="DieseArbeitsmappe.PbicThisWorkbook"
End Sub
So , the conclusion from that is adding slightly to what Dude_Scott said:
Application.Run allows you to run, from anywhere, both Private subs and Public subs that are anywhere.
Call only allows you to run from anywhere, Public subs that are anywhere
Ref:
https://stackoverflow.com/questions/31439866/multiple-variable-arguments-to-application-ontime
http://excelmatters.com/2017/04/07/passing-arguments-byref-using-run/
Example Workbook with my coding: MainFile.xls : https://app.box.com/s/prqhroiqcb0qccewz5si0h5kslsw5i5h
http://www.tushar-mehta.com/publish_train/xl_vba_cases/1022_ByRef_Argument_with_the_Application_Run_method.shtml

How to call a function in vba which is already assigned to a button

I tried to create a ribbon in excel and I was successful. Now I have assigned a macro to a button.
Function delete_cells(control As IRibbonControl)
Now, I created another macro, from which I need to call the function delete_cells. I tried to call it as below.
Function modify_cells(control As IRibbonControl)
delete_cells
End Sub
I am getting an error saying Argument not optional. Please help me with this error.
I suggest that you create a separate subroutine that you call from the button's OnAction and anywhere else you want to call it from, e.g.:
'button macro
Sub cmdDeleteCells_OnAction(control as iRibbonControl)
DeleteCells
End Sub
'another Sub that calls the delete routine
Sub SomeOtherSub
DeleteCells
End Sub
'the one they're all talking about
Sub DeleteCells
msgbox "All your cells are toast, bwah hah hah ha!"
End Sub
EDIT: If you really want to just call the button's OnAction sub, you need to pass it an iRibbonControl object as well, so declare a fake one:
Sub CallTheButtonsCode()
Dim FakeControl As IRibbonControl
cmdDeleteCells_OnAction FakeControl
End Sub
I really don't recommend this for code maintenance reasons, but it works.
In your Function delete_cells(control As IRibbonControl) you have an REQUIRED argument ...(control As IRibbonControl). When you call the function it should look like this :
Function modify_cells(control As IRibbonControl)
delete_cells(myControl) 'myControl is your variable. Define what control you want to pass to the function.
End Sub

Resources