How to stop a sub which calls upon multiple subs - vba

I have been creating a tool which extracts data from other Excel files and does various types of analysis. Because of these numerous different analyses, I have had to create multiple subs which are then all called upon in a "master" sub, which is activated when the user presses a button.
The issue I'm having is with error handling to stop the macro when there's an issue or when the user is trying to stop the code themselves - e.g. a pop-up box appears with a "continue or stop" type scenario. However, I can't work out how to make the macros stop! Using Exit Sub doesn't work because the master sub will then just jump to the next sub in the queue.
See example below - the user presses the button to activate master_sub. If within the sub1 stage the user selects "No" or "Cancel" in a MsgBox, sub1 will end (if using Exit Sub) but master_sub will then just go on to sub2 and sub3. How do I make master_sub stop based upon an input/action within sub1?
Sub sub1()
Dim MyMsg as Variant
MyMsg = msgbox("Do you wish to continue?", vbYesNo)
If MyMsg = 7 then 'If user clicks "No"
Exit Sub
Else
'Do stuff
End if
End Sub
Sub sub2()
'More stuff
End Sub
Sub sub3()
'Other stuff
End Sub
master_sub()
call sub1
call sub2
call sub3
End Sub

Instead of using Exit Sub Throw a custom exception and catch it in your master_sub.
Something like
Sub sub1()
Dim MyMsg as Variant
MyMsg = msgbox("Do you wish to continue?", vbYesNo)
If MyMsg = 7 then 'If user clicks "No"
Throw new MyException
Else
'Do stuff
End if
End Sub
master_sub()
Try
call sub1
call sub2
call sub3
Catch ex as MyException
'Do something if you want
End Try
End Sub

The "how does master_sub" detect when one of the child methods has been cancelled can be solved like this:
Sub master_sub()
Dim result As Boolean
result = Sub1()
If (result = False) Then
Exit Sub
End If
result = Sub2()
If (result = False) Then
Exit Sub
End If
result = Sub3()
If (result = False) Then
Exit Sub
End If
End Sub
Function Sub1() As Boolean
Return True
End Function
Function Sub2() As Boolean
Return False
End Function
Function Sub3() As Boolean
Return True
End Function
I've kept it very simple, but basically by changing from a Sub to a Function you can have each method return a value and decide in your main routine (master_sub) what to do depending on the value returned.

Related

VBA if in text is in textbox then do something

I've written the following code so that if a certain text exists in my listbox and "ok" button is clicked a certain thing is done.
Private Sub CommandButton3_Click()
If (Me.ListBox2.Text) <> ("PA") Then
Call macro1
ElseIf (Me.ListBox2.Text) <> "menu" Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
The problem is that when "ok" is clicked all events are still carried out even if the specified text isn't in the textbox.
You probably want to use = operator, and not <> operator. Also note that ListBox.List(i) is the correct way of getting selected item for single selection mode:
Private Sub CommandButton3_Click()
Dim SelectedItem = ListBox1.List(ListBox1.ListIndex)
If SelectedItem = "PA" Then
Call macro1
ElseIf SelectedItem = "menu" Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
Edit
Following your comment, you can create a function that looks for the existence of that item:
Private Function TextExists(text as String) as Boolean
Dim i as Long
For i = 0 To ListBox1.ListCount - 1
If ListBox1.List(i) = text Then
TextExists = True
Exit Function
End If
Next
TextExists = False
End Function
And then use this function in the main code like this:
Private Sub CommandButton3_Click()
If TextExists("PA") Then
Call macro1
ElseIf TextExists("menu") Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
N.B. I have written this manually here, without an IDE. Please check for indexes and other little things.

VBA Excel Sub continues running after Form was unloaded

I don't know what happened but after I implemented a new code the 'Sub' continues running after I press my 'Cancel' button.
My 'Cancel' Button:
Private Sub CancelButton_Click()
If Cancel Then Exit Sub
Cancel = True
'Save Settings
....
Unload Me
End Sub
My Sub and new function:
Sub Example()
Dim myArr() as string
...
some loops....
If Cancel Then
Exit Sub
End If
myArr = NewFunction(a1,a2)
myVar = myArr(1)
...
End Sub
Function NewFunction(a1,a2) As String ()
Dim tmpArr() as string
...
ReDim tmpArr(1)
...
NewFunction = tmpArr()
End Function
Issues:
If I avoid execution of line myVar = myArr(1) the code continues running after canceling, so I need to stop execution manually. Why?
If line myVar = myArr(1) is executed, the form will be unloaded and error appears:
runtime error 9
Subscript out of range
Don't get it what is wrong. The code runs ok if I'm not canceling it. The problem only is with 'Cancel' button which was working perfect before...
Cheers, Andy
Form buttons:
This is correct code which works for me:
Dim Cancel As Boolean
Private Sub UserForm_Initialize()
Cancel = False
End Sub
Private Sub CancelButton_Click()
SetStatus ("Cancelling")
If Cancel = False Then
Cancel = True
Exit Sub
End If
'Save Settings
'... some code
If Cancel Then
Unload Me
End If
End Sub
Sub Example()
'...
'some loops....
If Cancel Then
'close open files...
'... some code
SetStatus ("Cancelled")
Exit Sub
End If
'...
End Sub
When 'Cancel' button is pressed, the code will stop in safe manner. Then the second click on 'Cancel' button the form will be unloaded.

Access/VBA: "Run-time error 2169. You can't save this record at this time"

Using: Access 2013 with ADO connection to SQL Server back-end database
A form in my Access database is dynamically bound at runtime to the results of a SELECT stored-procedure from SQL Server, and allows the user to make changes to the record.
It has 2 buttons: Save and Cancel.
It is shown as a pop-up, modal, dialog form, and it has a (Windows) Close button at the top right corner.
I've put VBA code to ask the user whether he wants to Save, Ignore or Cancel the close action.
But there are problems and it gives the aforementioned error if Cancel is clicked. There are also other problems, like, after the error occurs once, then any further commands (Save or Cancel or closing the form) don't work - I think this is because the VBA interpreter has halted due to the earlier error. Another complication is that arises - I now need to end the MS-Access process from Windows Task Manager, doing this and then restarting the database and then opening this form will give an error and the form won't load. When the form is then opened in Design mode, I can see the connection string for the form is saved in the Form's Record Source property (this happens only sometimes), and which looks something like this:
{ ? = call dbo.tbBeneficiary_S(?) }.
Here is my code:
Dim CancelCloseFlag As Boolean
Dim SavePrompt As Boolean
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim a As Integer
If SavePrompt Then
a = MsgBox("Do you want to save changes?", vbQuestion + vbYesNoCancel, "Changes made")
Select Case a
Case vbNo:
Me.Undo
CancelCloseFlag = False
Case vbYes:
'do nothing; it will save the changes
CancelCloseFlag = False
Case vbCancel:
Cancel = True
CancelCloseFlag = True
End Select
End If
End Sub
Private Sub Form_Dirty(Cancel As Integer)
SavePrompt = True
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = acDataErrContinue
End If
End Sub
Private Sub Form_Load()
LoadBeneficiaryDetails
End Sub
Private Sub Form_Unload(Cancel As Integer)
If CancelCloseFlag Then
Cancel = True
End If
End Sub
Private Sub btCancel_Click()
If Me.Dirty Then
SavePrompt = True
End If
DoCmd.Close
End Sub
Private Sub btSave_Click()
SavePrompt = False
DoCmd.Close
End Sub
I'm stuck and would like to know how others go about this issue? Basically I want to offer the user the choice Save, Ignore, Cancel when the user attempts to close the form with either Cancel button or the (Windows) close button. If the user chooses Cancel, then it should just return to the form without changing or undoing any changes to the data. The solution may be simple but it escapes my overworked mind.
Thanks in advance!
Please try the following code - I tested against all six scenarios and the proper action is taken.
Option Compare Database
Option Explicit
Dim blnAction As Integer
Dim blnBeenThereDoneThat As Boolean
Private Sub Form_BeforeUpdate(Cancel As Integer)
If blnBeenThereDoneThat = True Then Exit Sub
blnBeenThereDoneThat = True
blnAction = MsgBox("Do you want to save changes?", vbQuestion + vbYesNoCancel, "Changes made")
Select Case blnAction
Case vbNo:
Me.Undo
Case vbYes:
'do nothing; it will save the changes
Case vbCancel:
Cancel = True
End Select
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = acDataErrContinue
End If
End Sub
Private Sub Form_Load()
LoadBeneficiaryDetails
End Sub
Private Sub Form_Unload(Cancel As Integer)
If blnAction = vbCancel Then
blnBeenThereDoneThat = False
Cancel = True
End If
End Sub
Private Sub btCancel_Click()
If Me.Dirty Then
Form_BeforeUpdate (0)
End If
If blnAction = vbCancel Then
blnBeenThereDoneThat = False
Exit Sub
ElseIf blnAction = vbYes Then
DoCmd.Close
Else
DoCmd.Close
End If
End Sub
Private Sub btSave_Click()
If Me.Dirty Then
Form_BeforeUpdate (0)
End If
If blnAction = vbCancel Then
Exit Sub
Else
DoCmd.Close
End If
End Sub

Terminating Macro From executing further on validation

I have a method-A() that is called from multiple methods,
On a condition in method-A, i have to terminate the macro.
i saw one option as Exit sub but this will just exit the current sub ie:method-A() and the remaining program continues.
how to handle this.
Sub mainMethod()
method-A()
end Sub
Sub method-A()
if (true) Then
'Terminate the macro. that is exit method-A() and also mainMethod()
end Sub
Edit after comment:
Just use end where you want to terminate ALL code.
Sub mainMethod()
method_A()
end Sub
Sub method-A()
if (true) Then End
'Terminate the macro. that is exit method-A() and also mainMethod()
end Sub
Original Answer: All you need to do is make methodA a function and return this function as FALSE if you want to exit the main method as per the following code:
Sub mainMethod()
'Run the custom function and if it returns false exit the main method
If Not method_A Then Exit Sub
'If method_A returns TRUE then the code keeps going
MsgBox "Method_A was TRUE!"
End Sub
Function method_A() As Boolean
Dim bSomeBool As Boolean
'Code does stuff
bSomeBool = True
'Check your condition
If bSomeBool Then
'Set this function as false and exit
method_A = False
Exit Function
End If
'If bSomeBool = False then keep going
End Function

VBA: subroutine with if statement and returning true or false?

SOLVED!
I have to validate that certain cells are not empty, so I want to create a subroutine and pass the variables I need checked.
This is what I came up with:
Sub errorMessage(errMsg As String, errRange As String)
If Range(errRange) = "" Then
MsgBox errMsg, , "Error:"
Range(errRange).Activate
'this is what i was looking for :doh:, the 'end' line terminates everything..
END
End Sub
Now when I call it from my button, will it actuall end the sub of the button?
i.e.
Private Sub CommandButton1_Click()
Call errorMessage("name is missing", "D4")
'this function shouldn't be called if there was a msgbox displayed with the above call
sendEmail
End Sub
How can i make this happen?
EDIT:
OK So this is how i sovled it, the reason i'm trying to do this is to avoid tons of lines of code in the buttonClick sub, what are your thoughts??
keep in mind that this thing has to check about 25 questions for blanks before executing the sendEmail sub....
Private Sub CommandButton1_Click()
Call validateEntry("Client Name is missing.", "D4")
Call validateEntry("# not complete.", "D5")
Call validateEntry("Address same as CV?", "D6")
Call validateEntry("Number missing.", "D8")
Call validateEntry("Type missing.", "D9")
Call validateEntry("Q1 requires a Yes or No.", "E19")
Call validateEntry("Q2 requires a Yes or No.", "E21")
Call validateEntry("Q3 requires a Yes or No.", "E23")
Call validateEntry("Q4 requires a Yes or No.", "E25")
Call validateEntry("Q5 requires a Date.", "D28")
Call validateEntry("Q6 requires a Yes or No.", "E30")
Call validateEntry("Q7 requires a Yes or No.", "E32")
MsgBox "passed"
'sendEmail
End Sub
Sub validateEntry(errMsg As String, errRange As String)
If Range(errRange) = "" Then
MsgBox errMsg, , "Error:"
Range(errRange).Activate
End
End If
End Sub
So, in your example, you're looking for the "passed" notification to only be sent when there is data in cell D4, right?
This should work:
Private Function errorMessage(errMsg As String, errRange As String) As Boolean
errorMessage = False
If Len(Trim(Range(errRange))) = 0 Then
MsgBox errMsg, , "Error:"
Range(errRange).Activate
errorMessage = True
End If
End Function
Public Sub CommandButton1_Click()
If errorMessage("name is missing", "D4") = False Then
MsgBox "passed"
End If
End Sub
Alternatively, you can handle all MsgBox notifications from within the function, to group similar logic together, and keep the Button Click Event Sub clean:
Private Function errorMessage(errMsg As String, errRange As String)
If Len(Trim(Range(errRange))) = 0 Then
MsgBox errMsg, , "Error:"
Range(errRange).Activate
Else
MsgBox "passed"
End If
End Function
Public Sub CommandButton1_Click()
Call errorMessage("name is missing", "D4")
End Sub
There are a number of misconceptions here.
First, no, it will not end the button routine by default. You will need to handle that within your button.
Next, you're missing an End If somewhere in here:
Sub errorMessage(errMsg As String, errRange As String)
If Range(errRange) = "" Then ' This may not be the best way to check for
' an empty range
MsgBox errMsg, , "Error:"
Range(errRange).Activate
Exit Sub
End Sub
You really don't even want a subroutine in the first place, you want a function that returns a boolean, like this:
Function errorMessage(errMsg As String, errRange As String) as Boolean
' Function returns True if an error occured
errorMessage = False
If Range(errRange) = "" Then
MsgBox errMsg, , "Error:"
Range(errRange).Activate
errorMessage = True
End If
End Sub
And then here:
Private Sub CommandButton1_Click()
If errorMessage("name is missing", "D4") Then
Exit Sub
End If
'this msgbox should not display if the above msgbox is displayed
MsgBox "passed"
' continue on with all of your fun processing here
End Sub