hey, first thanks to all for answering my other questions. I am extremely new to Excel VBA and some things I just get hung up on. I have a userform (not embedded in a worksheet) and I have a few fields that are for currency (amounts, etc) and if someone inputs a letter it errors after they hit the command button and they lose all info. I need error code to where I can tell them in a msgbox that they should not put characters in a currency field. I don't need it specific to those fields but I don't want them to lose there data when they hit the command button to dump the data into a spreadsheet.
How can I have them see the error msg, hit the ok button and have it take me right back to the screen without losing the data they have alread entered? Basically give them the opporunity to correct their error but not have to reinput 50 fields?
Thanks
Can't be specific without the actual code, but add error handlers to your code:
Sub SomeRoutine()
Dim stuff
On Error GoTo EH
' Code
Exit Sub
EH:
' Any errors with come here
If Err.Number = <specific errors to trap> Then
MsgBox "Oops..."
'As a debug tools, put a Resume here,
' but be sure to put a break on it,
' and don't leav it in the finished code
Resume
End If
End Sub
As I understand it you want the user to enter numeric numbers only into the text box - right? This is what I normally do.
In a global module add the following function:
Function IFF(c, t, f)
Dim v
If c Then v = t Else v = f
IFF = v
End Function
Then in your textbox_change event add the below:
Private Sub txtAmount_Change()
txtAmount.Text = IFF(IsNumeric(txtAmounto.Text), Val(txtAmount.Text), 0)
End Sub
This will basically put 0 in the box as soon as the user enters an invalid number.
Hope this helps
A slightly different take on error handlers than that given by chris neilsen
Sub SomeRoutine
On Error GoTo ErrHandler 'doesn't matter where you put it
'as long as it's before the code you want to protect
'Dim Stuff
'Do Stuff
ExitRoutine: 'Note the colon(:), which makes this a label
'Any cleanup that you _always_ want
Exit Sub
ErrHandler:
Select Case Err.Number
Case <some error you want to handle specially>
'special handling
Case Else
'default handling, which may include:
Resume ExitRoutine
End Select
Resume
End Sub
Note that that last Resume will never get hit in normal processing (if you've written your error-handling Cases correctly), but will let you set it as "Next Statement" when you're debugging in Break mode. This is an easy way to see exactly which statement threw the error.
Related
I am using VBA to add data validation to my cells. However instead of using the AlertStyle argument provided in the Add method, I want to create a custom error handler. Reason being, the AlertStyles provided by VBA do not force the user to abort cell editing if an incorrect value is added. The user can select "Retry" or "Cancel", and sometimes, when they hit "Cancel", all cell contents are lost (if there was only one value previously in the cell. If there was more than one value previously in the cell, no values are lost). So I'd rather create an error handler that exits the Sub when the user enters invalid data.
Here's my attempt:
Sub customised_validation_dates_2()
With ActiveSheet.Range("Date_Entry").Validation
On Error GoTo err
.Delete
.Add Type:=xlValidateDate, _
Operator:=xlBetween, Formula1:="01/01/2000", Formula2:="=TODAY()"
.IgnoreBlank = True
err: Exit Sub
End With
End Sub
but when an invalid date is entered, a message box still pops up, prompting the user to either retry data entry or cancel data entry, but like I said, if there is exactly one value previously in the cell and the user hits "Cancel", the cell value is lost.
extra context:
I have another macro running that allows the user to enter multiple values in one cell. So what I am trying to do is, if in the cell there is already another value, and the user tries adding another value that is invalid, I want to exit cell editing without giving the user the option to retry or cancel data entry, which is the root of my issue (because when they hit "cancel" and there is already a value in the cell, that other value gets erased).
I think you've gotten confused about your errors. Your code relating to errors (on error and err) is related to VBA errors and has nothing to do with cell validation.
To illustrate a error walkthrough example...
Sub exampleOfErrorHandling()
Dim aResponse As String
aResponse = InputBox("Enter something. Text will trigger an error while a nubmer will be accepted.")
If aResponse = "" Then Exit Sub
'programs procedure to jump to section problem with an error
On Error GoTo ProblemZone
Dim anyNumber As Integer 'variable will only accept number
anyNumber = aResponse
On Error GoTo 0 'sets errors to be handled in default method
MsgBox anyNumber & " is a valid number"
'section where other code would typically be inserted
Exit Sub 'where normal code would end
ProblemZone:
'section to handle errors
Dim tryAgain As Long
tryAgain = MsgBox(aResponse & " is not a number. Try again?", vbYesNo + vbCritical)
If tryAgain = vbYes Then Call exampleOfErrorHandling
End Sub
What it appears you want is something with cell validation. You might consider trying the record macro while setting validation to capture exactly what you want. Then engineer that to set the validation as you prefer.
Alternatively, you could use the change event to remove prior values if they don't meet requirement. Here's example of requiring cell A2 to be numeric. Note this must be in the sheet module, not the typical Modules section.
Private Sub Worksheet_Change(ByVal Target As Range)
'cell A2 must be a number
If Not Intersect(Target, Me.Range("A2")) Is Nothing Then
If Not IsNumeric(Me.Range("A2")) Then
MsgBox "A2 is not numeric"
Application.Undo
End If
End If
End Sub
This question already has answers here:
Why VBA goes to error handling code when there is no error?
(5 answers)
Closed last year.
Example Code:
On Error GoTo Line3
More code
Line3:
Some code
Some code
More code
Even when there is no error, the "some code" underneath Line3 still gets run even though I don't want it to run. Otherwise when there is an error, Line3 gets run appropriately (skipping code that comes before it like it should).
Your code runs as it should. Code dealing with errors should be written outside of the main procedure, keeping in mind that your code should also try and spot potential errors and deal with them before they cause an error.
At the moment your error isn't being cleared when you jump to it, and because it's in the main body of code it's executed when your first set of More Code is finished and it reached the Line3 label.
Sub Test1()
'Ignore the error.
'MsgBox won't appear, but code won't know an error occured.
'MsgBox says all's good anyway, even though the error is still present.
Dim Rng As Range
On Error GoTo SkipLineWithError
MsgBox Rng.Address
SkipLineWithError:
MsgBox "All good, error number is " & Err.Number
End Sub
A better way is to try and catch the error before it happens:
Sub Test2()
'Checks that Rng won't throw an error if referenced.
'Code has dealt with the error and says all's good.
Dim Rng As Range
If Not Rng Is Nothing Then
MsgBox Rng.Address
Else
MsgBox "Range not set"
End If
MsgBox "All good, error number is " & Err.Number
End Sub
Sometimes, errors do occur though and they need to be dealt with properly. For this you jump out of the main procedure, deal with the error and jump back in again.
With this code notice the Exit Sub - the code between Exit Sub and End Sub is where your error handling goes. The main body of code ends when it reaches Exit Sub.
Resume tells your code where to jump back to - on its own it jumps back to the line that caused the error, Resume Next is the line after the error and Resume <label> jumps back to a label you've entered such as Resume Line3
Sub Test3()
Dim Rng As Range
On Error GoTo ErrorHandler
MsgBox Rng.Address
MsgBox "All good, error number is " & Err.Number
TidyExit:
'Close connections, general tidy up before ending procedure.
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 91 'Deal with the error if it happens.
'For this we'll give Rng a default address.
Set Rng = Sheet1.Range("A1")
Resume
Case Else
MsgBox "Error couldn't be handled... display an error message."
Resume TidyExit 'Jump to end of main body of code.
End Select
End Sub
Edit: Have updated code based on comment by #VBasic2008. Was being lazy with my first code and missed a key point.
I've barely scraped the surface here, the links below should help.
on-error-statement
vba-error-handling
Using Gotos to manage execution paths is almost always a poor design choice. You could consider the following approach that does not rely so heavily on Goto labels. It is easier to see the intended logic flow (and error handling) without having to visually parse any Goto statements.
Sub Example()
If Not TryMorecode1() Then
Somecode1
Somecode2
End If
Morecode2
End Sub
Private Function TryMorecode1() As Boolean
'catch errors locally within this function
'return False if an error is generated
End Function
Private Sub Morecode2()
End Sub
Private Sub Somecode1()
End Sub
Private Sub Somecode2()
End Sub
The code will still run, it's just a label you can point the execution to if need be.
Try this to avoid your problem but without knowing your exact requirement, you’ll likely need to tweak the code structure to suit your needs.
My suggestion would be to put Line3 at the bottom, not in the middle of the routine. That way you just do a clean up and then get out. It tends to make more sense from a readability perspective. Of course, you should only do that if it makes sense to get out after the error occurs.
On Error GoTo Line3
More code
Goto Line4
Line3:
Some code
Some code
Line4:
On Error GoTo 0
More code
The trick with GoTo statements (if you intend to use them) is to use them sparingly.
I'm working with excel vba since 3 month now and this is (after one course of programming in university) my first real contact to programming. Please take that to account.
I built up a userform with many textboxes. Therefore I wrote a makro which first checks if the user put in a value in every textbox so that afterwards the procedure begins. If there is not a value in every textbox I want the exit sub after msgbox the user to fill again every textbox. Quiet simple, right?
I thought the best way to manage this is using the Go to-statement. After showing my boss the code he told me I should never use this statement to avoid some sort of spaghetti code. He told me a real programmer would never use this statement and would try to work his way around. This is what my code looks like:
Private Sub SaveButton_Click()
Dim i As Integer
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then: GoTo again
Next
'procedure...
Exit Sub
again:
MsgBox "Please fill in every mandatory textbox"
End Sub
My question: is it right to avoid this statement in every situation? Is it really some sort of unspoken rule to never use that statement? What are the Pros and Cons of this, and which are my alternatives(especially in this case)?
I appreciate every helpful answer. Thank you!
Your code can be easily re-written as below:
Private Sub SaveButton_Click()
Dim i As Integer
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then
MsgBox "Please fill in every mandatory textbox"
Exit Sub
End If
Next
End Sub
Don't ever use GoTo unless it is behind On Error … or not avoidable. If there is any chance to avoid GoTo, then avoid it. It makes your code hard to maintain and is considered to be a bad practice.
As GSerg pointed out there might be rare cases where GoTo cannot be avoided. Eg. using GoTo for emulating missing language constructs (e.g. VBA lacks the Continue keyword) and exiting deeply nested loops prematurely.
Could be rewritten thus. So below the goto is replace by an Exit For and then a subsequent test. Avoid goto unless in an On Error Goto <lable> statement.
Private Sub SaveButton_Click()
Dim i As Integer
Dim bGut As Boolean: bGut = True
'mandatory textboxes:
For i = 1 To 13
If UserForm1.Controls("Textbox" & i) = "" Then
bGut = False
Exit For '* skip out
End If
Next
If Not bGut Then
MsgBox "Please fill in every mandatory textbox"
Else
'* start processing
End If
End Sub
I wish to pause the execution of my VBA code once an error appears, and continue the execution when corrected?! Because I have a very long execution, so it always goes from the beginning...
you need to use an error handler. Something like
On Error GoTo errorTrap
at the beginning of your code directly after your dims and other setup.
Then for the actual errortrap you would write this before the End Sub.
The whole thing would look like this.
Sub test()
Dim v As Variant, x As Integer 'etc etc
On Error GoTo errorTrap
'run your code here. An example is below
x = "hello" 'this will create an error since hello is not an integer
MsgBox "finished"
End 'ignore the error trap when done
errorTrap:
Debug.Print Err.Description
Stop 'stop here and figure out what is going on.
'any other code needed to fix the error
Resume Next
End Sub
I have a Function that has some bug in it somewhere causing it to return #VALUE when I try to execute it in excel.
I have no idea where the error is, and stepping through the code is just tedious. So I'd like the debugger to break as soon as an error occurs.
I tried going to Tools->options->General->"Break on All Errors" but noticed no change.
How do I get the VBA IDE to break on an error?
Just add an error handler in your function like the one below.
If an error occurs, the IDE will print the error description in the immediate window and stop on Debug.Assert 0.
Then press F8 two times to go to the line where the error occured.
Function Test() As Variant
On Error GoTo ErrHandler
Dim v(), n&, r&, c&
For r = 1 To 3
For c = 1 To 4
n = n + 1
ReDim Preserve v(1 To r, 1 To c)
v(r, c) = n
Next c
Next r
Test = v
Exit Function
ErrHandler:
Debug.Print Err.Number & ": " & Err.Description
Debug.Assert 0
Resume
End Function
Something like:
Public Function dividddeee(a As Variant, b As Variant) As Double
On Error GoTo wtf
dividddeee = a / b
Exit Function
wtf:
On Error GoTo 0
MsgBox "Houston, we've had a problem here"
MsgBox a & vbCrLf & b
End Function
If you add error handlers, you can take advantage of Debug.Assert to force a break if you don't want the standard handler to execute. You can define a compiler constant to just let your error handlers deal with it when you release it to the wild. If you want to see what specific line caused the error, you can put Resume Next after the Debug.Assert. When you step through it, it will take you to the line immediately after the one that caused the error.
Drop this small demo into Module and run the Sub with both Release = True and Release = False for an example:
Option Explicit
#Const Release = False
Private Sub Demo()
Debug.Print DivByZero(5)
End Sub
Public Function DivByZero(inValue As Integer) As Double
On Error GoTo Handler
DivByZero = inValue / 0
Exit Function
Handler:
#If Release Then
MsgBox Err.Description & " in DivByZero"
#Else
Debug.Assert False
Resume Next
#End If
End Function
If you call a VBA function as UDF the settings of the VBA IDE are not involved. So no chance for error debugging this way.
Try calling the function from a test Sub. Then the error debugging will work.
But there are some things a function cannot do as a UDF but can do called from a Sub. If one of those things is the reason for the #VALUE error, then no way around setting a breakpoint in the function and stepping forward until the next step is not possible. The last line in stepping is the error line.
You should really mention if the function is
Called from an Excel cell.
Has an event handler.
Show how your variables are declared.
If called from a cell, the inputs to the function can cause you problems with different inputs. Sometimes preventing the call of the function if the types significantly change to something unexpected. For example a parameter declared as variant in its signature will pass in an error but fail in the function. You may be trapping this error and returning #VALUE in the code. No way for us to know that.
If you have an event handler, for a quick test, you could put a 'Stop' in the event handler to stop like you are asking. If not you can put one in as already stated. Assertions are nice, I like them and use a lot of them - but here since you know the function and are working on this explicit problem a Stop should be good enough for your testing. Just don't save it for production code. Make a Test copy of the book.