Nested Macros: Scope: how to exit all? - vba

I current have 2 Macros:
The second macro is called within the first to perform a task. However I have logic within the second macro that states if my variable LastRow < 3 then exit the sub. This of course takes us immediately back into macro 1. What I desire here is to then exit immediately macro 1 as well. The way I have attempted to do this is by making LastRow public within both macros.. so when we exit back into macro 1, we have:
sub macro1()
application.run("macro2")
if LastRow < 3 then
exit sub
end sub
where macro 2()
sub macro1()
Static LastRow As Long
if LastRow < 3 then
exit sub
else do something
end if
end sub
I believe I may the issue may be that Static is not giving macro 1 access to variable LastRow.
whats the best way to proceed?
Regards!

You could use End statement in this way:
sub macro2()
Static LastRow As Long
if LastRow < 3 then
'...here is End
End
else
'do something
end if
end sub
However, End has some disadvantages you should be aware of. Let me cite them base on MSDN:
Terminates execution immediately. Never required by itself but may be
placed anywhere in a procedure to end code execution, close files
opened with the Open statement and to clear variables.
When executed, the End statement resets all module-level variables and
all static local variables in all modules. To preserve the value of
these variables, use the Stop statement instead. You can then resume
execution while preserving the value of those variables.
The End statement provides a way to force your program to halt. For
normal termination of a Visual Basic program, you should unload all
forms. Your program closes as soon as there are no other programs
holding references to objects created from your public class modules
and no code executing.

You could use a Function instead of a Sub and return a Boolean for example.
Function macro2() As Boolean
'returns false if the last row is 2 or less, true otherwise
LastRow As Long
if LastRow >= 3 then
macro2 = True
'do something
end if
End Function
Then in your first macro:
sub macro1()
if Not macro2 Then Exit Sub
end sub

Declare the variable in the first macro and pass it ByRef to the second macro.
Sub Mac1()
Dim lLastRow As Long
Mac2 lLastRow
If Not IsTooBig(lLastRow) Then
'do stuff
End If
End Sub
Sub Mac2(ByRef lLastRow As Long)
lLastRow = 5
If IsTooBig(lLastRow) Then
Exit Sub
End If
End Sub
Function IsTooBig(ByVal lLastRow As Long) As Boolean
IsTooBig = lLastRow >= 5
End Function
ByRef means that whatever changes you make to lLastRow in Mac2 will be reflected in lLastRow in Mac1.

Related

Interrupt macro to process keystrokes for smooth user experience

I'm working on a macro that displays cell info on the statusbar on each selection change within the excel application. It has grown with the addition of new features thanks to help from members in here. So now sometimes the focus sticks to a cell and I cant move on with the arrow keys to nearby cell before the cell info to be displayed has been calculated. But I do want the selection to move on for a smooth user experience. How should I interrupt the calculation?
It goes something like this in a class module:
Private Sub Appl_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Dim InfoAboutTarget
InfoAboutTarget = GetInfoAbout(Target)
WriteInfoToStatusbar(InfoAboutTarget)
End Sub
Is any of the following two options any good?
Have an application_onkey event raising an error flag with a user-defined error code and an error handler in the above sub that exits on this particular error
Measure the time elapsed since the above sub started at interesting points in the code and exit after a time large enough to threaten the user experience?
As Dirk says, you should use DoEvents to allow excel to process user input, and detect if you long running code is interrupted and if so, cancel the interrupted execution, while allowing the most recent call to complete.
Here's an example to demonstrate (coded in ThisWorkbook)
Option Explicit
Dim abort As Boolean
Dim CallCount As Long
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
test Target
End Sub
Sub test(r As Range)
Dim i As Long, n As Long, b As Long
CallCount = CallCount + 1
n = 100
' simulate long running code
For i = 1 To n
DoEvents
If abort Then
GoTo AbortSub
End If
Application.StatusBar = CallCount & " " & r.Address & " " & i
Next
AbortSub:
' Abort all interrupted calls
If CallCount > 1 Then
abort = True
Else
abort = False
End If
CallCount = CallCount - 1
End Sub
Not sure if it will work, but maybe something like this
Dim globalCounter As Long ' 0 by defaul
Private Sub Appl_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
globalCounter = globalCounter + 1
WriteInfoToStatusbar GetInfoAbout(Target)
End Sub
Function GetInfoAbout(Target As Range)
Dim localCounter As Long
localCounter = globalCounter
' .. some code
DoEvents
If localCounter <> globalCounter Then Exit Function
' .. some code
End Function

VBA How to increment a number and return it to use again?

it's kinda similar to a loop but i want to have an input after every increment.
in short, i have a textbox linked to a cell in Excel. i wrote up a script that compares the value in the textbox to the sheet to tally and put a tick in the cell.
however after that i want to move a cell down as well as checking the next value.
so far, i've been trying for, while.. loops but it just keeps looping before i had a chance to enter the next value to compare.
so is there a way i can code it to make the loop pause, and then cont after i enter the value and comparing it?
something like this
Option Explicit
Public lngRowNumber As Long
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
TESTING lngRowNumber
lngRowNumber = lngRowNumber + 1
End Sub
Private Sub UserForm_Initialize()
lngRowNumber = 1
End Sub
Private Sub TESTING(ByVal lngRowNumber As Long)
MsgBox "Updated Row : " & lngRowNumber
End Sub

Running at once multiple Subs from an unique VBA Sub

I have three different Subs available in a VBA module and wanted to call those series of Subs from an unique Sub activated through a VBA button.
Below the code running:
Sub Updateworkbook()
Call Unprotectworkbook
Call CopyAndPaste
Call Protectworkbook
End Sub
After the first Sub Unprotectworkbook() is run the other Sub are not called and executed. Why this happens?
Below the Unprotectworkbook() Sub code for your reference
Sub Unprotectworkbook()
Dim myCount
Dim i
myCount = Application.Sheets.Count
Sheets(1).Select
For i = 1 To myCount
ActiveSheet.Unprotect "password"
If i = myCount Then
End
End If
ActiveSheet.Next.Select
Next i
End Sub
Modify your code as follows (change End to Exit Sub):
Sub Unprotectworkbook()
Dim myCount
Dim i
myCount = Application.Sheets.Count
Sheets(1).Select
For i = 1 To myCount
ActiveSheet.Unprotect "password"
If i = myCount Then
Exit Sub
End If
ActiveSheet.Next.Select
Next i
End Sub
or you can simply change it to the next one:
Sub Unprotectworkbook()
Dim sh
For Each sh In Sheets
sh.Unprotect "password"
Next
End Sub
It is very hard to answer your question without seeing the code in all three subs.
Some pointers though:
You don't need to select each sheet in order to modify it - just use Sheet(i).Unprotect "password" in the for loop instead.
Also, since you have a for loop you don't need to code when it should end, if you have defined the For i = 1 To myCount statement correctly. In other words, remove the If i = myCount Then End part.
You could define the For loop like the following: For i = 1 To Application.Sheets.Count to simplify your code, then you can remove the myCount variable.
You should always define your variables with a datatype in order to minimize errors, e.g use Dim i As Integer instead.
Always use Option Explicit at the top of each module, also to minimize confusion and errors caused by typos etc.
I strongly advise you to run through a couple of tutorials on VBA, there are lots around. The following is just the first one up when searching, I haven't tried it: Excel VBA Basic Tutorial 1
If this helps, I recommend making another set of 3 subs to test blank items first. Otherwise use one of the other answers above.
Sub msgTEST0() 'Call msgTEST0
Call msgTEST1
Call msgTEST2
Call msgTEST3
End Sub
Sub msgTEST1()
MsgBox "MSG1" & Space(10), vbQuestion
End Sub
Sub msgTEST2()
MsgBox "MSG2" & Space(10), vbQuestion
End Sub
Sub msgTEST3()
MsgBox "MSG3" & Space(10), vbQuestion
End Sub

One line macro to stop other macro by name

I know..
Application.Run ("Realcount2")
will call a macro. Can you end a macro with something like
Application.Stop("Realcount2")
You might be able to do something like this, but I'm not sure how reliable it will be. It seems to work for me on some simple tests, though.
Declare a public variable like StopMacro
Public StopMacro as Boolean
Assign this macro to your Button/etc, which sets a public variable StopMacro.
Sub SetStopMacro()
StopMacro = True
End Sub
Here is an example loop structure. Use DoEvents and within the loop, each iteration, check the value of StopMacro and ExitSub if true.
Sub Macro1()
Dim r As Long
StopMacro = False
For r = 1 To 100000
DoEvents
If StopMacro = True Then Exit Sub
'''''''''''''''''''''''''''''''''''''
' '
' Your code inside the loop '
' '
'''''''''''''''''''''''''''''''''''''
Next
End Sub

Macro to Clear multiple Columns

Below is my code that automatically runs the TimeStamp Macro when I open the workbook. However, it only runs if the Time is 6:45 am or later. My problem is that I am trying to get it to clear the cell contents in Columns A through D from Row 2 and on if the time is before 6:45 am and before it runs the TimeStamp macro. I tried to do it with just Column A and the debugger says that the top line is an issue. I can't seem to figure out why it is an issue though. I assume I am breaking some sort of syntax rule but I am not sure.
Private Sub Workbook_Open()
If Time > TimeSerial(6, 45, 0) Then
Call TimeStamp
Else
Sub Clear()
Dim wb3 As Workbook
Set wb3 = ThisWorkbook
With wb3.Worksheets("Avnet")
.Range("A2:A" & .Range("A2").End(xlDown).Row).ClearContents
End With
End Sub
Application.Wait "06:45:00"
Call TimeStamp
End Sub
It worked prior to me adding in the Sub Clear() section...
UPDATE:
Just tried making the Sub Clear() a separate module and using the call Clear() but that didnt work either. Here is what it looked like
The Clear () Code
Sub Clear()
Dim wb3 As Workbook
Set wb3 = ThisWorkbook
With wb3.Worksheets("Avnet")
Range("A2:A" & .Range("A2").End(xlDown).Row).ClearContents
End With
End Sub
The Part to call the Clear()
Private Sub Workbook_Open()
If Time > TimeSerial(20, 0, 0) Then
Call TimeStamp
Else
Call Clear
Application.Wait "19:35:00"
Call TimeStamp
End Sub
Declaring a sub within a sub will not work (I'm 99.9999% sure, but it's never entered my head to try this!)
Move your Sub Clear declaration so that it's after Workbook_Open, and then just call Clear from within Workbook_Open, in the same way that you call TimeStamp. (You're also missing an End If' at the end of WorkBook_open)
So your code would become:
Private Sub Workbook_Open()
If Time > TimeSerial(6, 45, 0) Then
Call TimeStamp
Else
Call Clear
Application.Wait "06:45:00"
Call TimeStamp
End If
End Sub
Sub Clear()
Dim wb3 As Workbook
Set wb3 = ThisWorkbook
With wb3.Worksheets("Avnet")
.Range("A2:A" & .Range("A2").End(xlDown).Row).ClearContents
End With
End Sub
As an aside, your Workbook_Open code can be simplified to:
Private Sub Workbook_Open()
If Time <= TimeSerial(6, 45, 0) Then
Call Clear
Application.Wait "06:45:00"
End If
Call TimeStamp
End Sub