VBA prevent closing of UserForm with "End" in code - vba

In my spreadsheet, I have a UserForm that is supposed to be open at all times.
Once in a while, my code will contain an "End" where I exit the code based on some if statement.
The problem is that this closes the UserForm, is there a way to prevent this from happening?
EDIT:
Sub Test1()
'Random code
Call Test2(Variable)
'Random code
End Sub
Sub Test2(ByVal Variable as Double)
If Variable = 0 then
'Random code
End If
If Variable = 1 then
Call Test3
End 'Original placement of End
End If
End Sub
Sub Test3()
'Random code
End Sub
This is a rough example of how the code is build (its rather long at this point). So depending on the "variable" different things happen in Test2. But if the Variable is 1, then the "random code" back in Test1 can't be executed thus, so I have to stop the code. I tried replace "End" with "Exit Sub" this only stops the code in Test2 from running, is it will give me an error when it goes back to Test1.
EDIT2:
Test1() is actually four different subs (at this point, more will be added) that all call Test2(). That is why I choose to split it up into so many subs and call them from within the subs.

No, not if you insist on using End. This will essentially have the same effect as clicking the "Stop" button in the developer window. You should (most likely) not be using End. I cannot tell you what you should be using, since I do not know what you are trying to achieve.
Update:
Based on your code, I don't see any reason for Test3() to be nested within Test2(), since it runs either the random code or Test3() (never both). Is there anything preventing you from splitting all the different cases into different subs, and then doing the If statement in the main sub?
Sub Main()
If Variable = 0 Then
'Random code from before Test2()
'Random code from Test2()
'Random code from after Test2()
ElseIf Variable = 1 Then
Call Test3()
Else
MsgBox "Variable must be 0 or 1!"
End Sub

You somehow need to tell Test1 that it needs to stop. One approach to this problem is to change your subs to functions and return a value indicating status. Something like this would work:
Function Test1() As Integer
Dim i As Integer
'Random code
i = Test2(Variable)
If i = 1 Then Exit Function
'Random code
End Function
Function Test2(ByVal Variable As Double) As Integer
Test2 = 0
If Variable = 0 Then
'Random code
End If
If Variable = 1 Then
Call Test3
Test2 = 1
Exit Function
End If
End Function
Function Test3() As Integer
'Random code
End Function

End closes anything and kills all the variables and objects that you have.
This is probably the worst way to end any sub and most probably you do not need it.
What's the deference between "end" and "exit sub" in VBA?
https://msdn.microsoft.com/VBA/Language-Reference-VBA/articles/end-statement

Related

VBA Can I count the number of times I call a function in my Script

I'm trying to build a dynamic progress bar in Excel, and the way I'm currently doing this is by hard-coding a number of steps into my script as the "total number of steps"... the Application.StatusBar code gets updated with a counter that is divided by this total.
What I'd like to do is to have "TotalSteps" pre-populate with 3 (using the example below) by searching ahead through the code to identify the number of times "Call fnProgress" appears in the subroutine.
Public Sub Example()
TotalSteps = 3 'Enter a value here equal to the number of times "Call fnProgress" appears below
Call ABC
Call fnProgress
Call 123
Call fnProgress
Call DoReMi
Call fnProgress
End Sub
I don't get the impression this is possible in VBA, but I figured someone here would know how to do it if it was! Or perhaps someone could offer a better solution to my simple progress bar... Being able to search ahead in the actual code is also an interesting problem, to me, and I'd imagine it's got further application beyond my status bar.
Thanks so much!
-Julia :)
It is possible, but it's not pretty.
I would consider the solution below bad practice in most cases. Anyway - here it goes:
Sub Test()
Dim StepCount As Integer
Dim TotalSteps As Integer
Steps = Array("Abc", "Def", "Ghi")
TotalSteps = UBound(Steps) + 1
For Each Step In Steps
StepCount = StepCount + 1
ActiveSheet.Evaluate Step & "()+0" '+0 is workaround to handle a bug in VBA. Without it, the method is called twice
Progress StepCount, TotalSteps
Next
End Sub
Sub Progress(StepCount As Integer, TotalSteps As Integer)
Debug.Print StepCount & "(" & TotalSteps & ")"
End Sub
Sub Abc()
Debug.Print "Abc"
End Sub
Sub Def()
Debug.Print "def"
End Sub
Sub Ghi()
Debug.Print "ghi"
End Sub
The base for this is the Evaluate function, that allows you to evaluate expressions in strings. The Test method calls the methods in the Steps array, using Evaluate.
Here's a different approach. Not a good way to write code, but again, there are situations where it is warranted.
Start by adding a reference to Microsoft Visual Basic for Applications Extensibility, then add this code:
Sub Test()
Dim CodeMod As VBIDE.CodeModule
Set CodeMod = ActiveWorkbook.VBProject.VBComponents("Module1").CodeModule
Debug.Print CountOccurrences("Call fnProgress", CodeMod.Lines(1, CodeMod.CountOfLines)) - 1 'Remove 1 to ignore this line
End Sub
Function CountOccurrences(SoughtString As String, InString As String) As Long
CountOccurrences = (Len(InString) - (Len(Replace(InString, SoughtString, "")))) / Len(SoughtString)
End Function
The module name is hardcoded. It reads the code, counts the number of occurences of Call fnProgress and subtracts one (so that the count it self isn't counted). Adjust as needed.

Using a variable as the name of a macro

This might be a fairly easy "no you can't" answer. But if any of you know of a way to make this work, I would really appreciate it.
I have a macro called "DES" and another called "FA". When I change a cell to either "DES" or "FA" I want it to run either the "DES" or the "FA" macro. I do not what to use a series of if/else logics to figure out the macro to run. I would rather have it so the Go_to_macro() function would feed in the variable and run the function. The reason for this is that I will eventually have dozens of macros that I would like to run this way.
Example code below: "Call command" is where there is a problem.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("command_des").Value <> Sheets("Back End").Range("command_be").Value Then
Dim command, number
command = Range("command_des").Value
ticker = Range("number_des").Value
Call Go_to_macro(command, number)
Else
End If
End Sub
//////////////////////////////////////////////////////////////////////////
Public Function Go_to_macro(command, number)
Range("command_be").Value = command
Range("command_" & command).Value = Range("command_be").Value
If Range("number_" & command).Value <> Range("number_be").Value Then
Call command
Else
End If
End Function
//////////////////////////////////////////////////////////////////////////
Sub DES()
Cells(1,1).Value = "Hello World!"
End Sub
/////////////////////////////////////////////////////////////////////////
Sub FA()
Cells(1,1).Value = "Goodbye World..."
End Sub
/////////////////////////////////////////////////////////////////////////
Ideas on a work around?
In the end I should get "Hello World" in cell A1 if I write "DES" into the namedrange or "Goodbye World..." in cell A1 if I write "FA" into the same namedrange. Just so you are clear on the outputs.
You can use
Application.Run command
(FYI, the Call keyword is obsolete and should not be used.
Instead of
Call Go_to_macro(command, number)
use
Go_to_macro command, number
)

VBA Type Mismatch when application is started

I'm having some trouble with a display object that I use to trigger a sub. The results of the display object are either true or false, and I use the _Change method. The code is quite simple.
Private Sub clamshellLblRequest_Change()
If Not tagDisplay Is Nothing Then
GoTo execute
Else
Set tagDisplay = LoadedDisplays
GoTo execute
End If
execute:
If clamshellLblRequest.Value = 1 Then
LogDiagnosticsMessage "Requesting clamshell label information"
Call labels.clamshell
End If
End Sub
When I first start the application, I get a "type mismatch" error (13) specific to this value. I have several other display objects that I use the same way with the same datatype but don't seem to have this problem. What else could be causing this?
Update:
I have a module I use standard timers with that include the following.
Public Sub tenthSec()
'Create a program delay, DateTime Timer resolution in MSWindows is 0.01. Needed for tag updates.
t = Timer
While Timer - t < 0.1
Wend
End Sub
When I execute call timers.tenthSec just before evaluating the value of the object, it doesn't seem to throw the type mismatch.
...
execute:
Call timers.tenthSec
If clamshellLblRequest.Value = 1 Then
LogDiagnosticsMessage "Requesting clamshell label information"
Call labels.clamshell
End If
End Sub
I wouldn't call this a solution, perhaps a band-aid. Any thoughts?
Agree with #Masoud about the wait. You could also use DoEvents inside of a loop, which allows other things to keep calculating, etc. Also, you shouldn't need the execute: and goto with the code you have, you should be able to just do something like this (note the change of Not Is Nothing to Is Nothing):
Private Sub clamshellLblRequest_Change()
If tagDisplay Is Nothing Then
Set tagDisplay = LoadedDisplays
End If
Application.Wait(Now + #0:00:01#)
' or
For i = 1 to 1000
DoEvents
Next i
If clamshellLblRequest.Value = 1 Then
LogDiagnosticsMessage "Requesting clamshell label information"
Call labels.clamshell
End If
End Sub

Error calling macro from userform

I have a userform to call a macro in a separate module when a button is clicked. I get the following error: "Run-time error '450': Wrong number of arguments or invalid property assignment"
In troubleshooting I removed the arguments and changed the dummy macro I was calling to not take arguments, but I get the same error.
Public Sub btnSubmit_Click()
Dim Description As String
Dim Priority As String
If (checkCleared.Value = False) Then
MsgBox ("Please certify that all sensitive informationhas been removed and then submit")
Exit Sub
Else
'Description = formScreen.txtDesc.Value
'Priority = formScreen.comboPriority.Value
'Application.Run ThisOutlookSession!postScreenedEmail(Priority, Description)
Application.Run ThisOutlookSession!postScreenedEmail
End If
End Sub
In the separate module:
Public Sub postScreenedEmail() '(Priority As String, Description As String)
MsgBox ("postScreened")
'MsgBox ("Priority is: " & Priority & " and Description is " & Description)
End Sub
I have tried other methods of calling the macro such as "Call postScreenedEmail()" but it cant see the macro then. My end goal is just to grab values from the userform and pass them to the other macro so they can be used with the API I am working with.
Edit: I may have mixed my terminology, this is the hierarchy I am working with (can't post pic with my rep). That being said I tried to do the call with just Application.Run "postScreenedEmail", Priority, Description and it changed nothing
-Project1(VbaProject.OTM)
-Microsoft Outlook Objects
| ThisOutlookSession
-Forms
| formScreen
|
-Modules
Module1
Try:
call postScreenedEmail
instead of:
Application.Run ThisOutlookSession!postScreenedEmail
Since your sub is public, vba should be able to find it without the module reference.
If this works, add the reference again (makes your code more readable, especially for others, as ckuhn203 pointed out in the comments) and see if it breaks. If so, that's where the problem is.
EDIT:
Are you sure you're referencing the right module?
If I try:
-Project1(VbaProject.OTM)
-Microsoft Outlook Objects
| ThisOutlookSession
-Modules
| Module1
in Module1:
Sub jzz()
Debug.Print "test"
End Sub
and in ThisOutlookSession:
Sub test()
Call Module1.jzz
End Sub
it works. No problem. Using:
Application.Run Module1.jzz
instead of Call trows a compile error.
Even:
Sub test2()
Call ThisOutlookSession.test
End Sub
from Module1 works, without problems.
Can you run such small tests to try to get the references right?
Try this... Application.Run takes a string for procedure name, and then comma-separated list of parameters/arguments:
Application.Run "Procedure_Name", arg1, arg2, arg3
So I think this should work:
Application.Run "ThisOutlookSession!postScreenedEmail", Priority, Description

Need VB to make Excel calculate a sheet or range in realtime and in the background

How can I make excel continuously calculate a sheet/range in realtime (not 1 calc/sec) and do it in the background?
I want this metric clock to run like a stopwatch....
=IF(LEN(ROUND((HOUR(NOW())*(100/24)),0))=1,"0"&ROUND((HOUR(NOW())*(100/24)),0),ROUND((HOUR(NOW())*(100/24)),0))&":"&IF(LEN(ROUND((MINUTE(NOW())*(100/60)),0))=1,"0"&ROUND((MINUTE(NOW())*(100/60)),0),ROUND((MINUTE(NOW())*(100/60)),0))&":"&IF(LEN(ROUND((SECOND(NOW())*(100/60)),0))=1,"0"&ROUND((SECOND(NOW())*(100/60)),0),ROUND((SECOND(NOW())*(100/60)),0))
I've used the following to produce the effect you are looking for:
Option Explicit
Public TimerRunning As Boolean
Dim CalculationDelay As Integer
Public Sub StartStop_Click()
If (TimerRunning) Then
TimerRunning = False
Else
TimerRunning = True
TimerLoop
End If
End Sub
Private Sub TimerLoop()
Do While TimerRunning
'// tweak this value to change how often the calculation is performed '
If (CalculationDelay > 500) Then
CalculationDelay = 0
Application.Calculate
Else
CalculationDelay = CalculationDelay + 1
End If
DoEvents
Loop
End Sub
StartStop_Click is the macro that I tie to the Start/Stop button for the stopwatch. You can get fancy, and change its name to "Start" or "Stop" depending on the value of TimerRunning, but I kept things simple to illustrate the concept.
The two key things here are:
Application.Calculate
Which forces Excel to calculate the worksheet, and:
DoEvents
Which allows VBA to run in the background (i.e. Excel does not stop responding to user input). This is what allows you to still press the "Stop" Button even though the timer is running.
I think this might fail your "(not 1 calc/sec)" criteria, but I achieved something similar as follows. Assumes your formula is in cell A1 of a worksheet named Sheet1.
In the ThisWorkbook code module:
Private Sub Workbook_Open()
Application.OnTime Now + TimeValue("00:00:01"), "RecalculateRange"
End Sub
... and in a regular code module:
Public Sub RecalculateRange()
Sheet1.Range("A1").Calculate
Application.OnTime Now + TimeValue("00:00:01"), "RecalculateRange"
End Sub