VBA MsgBox causes an erro - vba

In my VBA project I have the occasional MsgBox pop up to notify the user something has 'Completed' or 'Updated' after a subroutine has run.
It seems to run okay without the MsgBox, but inserting one seems to give me an error.
Not sure if it's necessary to display the entire code here as it's quite big but at the end of a subroutine I simply want ...
MsgBox ("Completed")
which is followed by the End Sub
However when I run this and then click on OK on the Msgbox, I get a runtime error which on clicking DeBug, it highlights the End Sub.
Is there any reason why having this would throw up such an error?
Am I missing something from it?
Many thanks
Some of the code here
'Add unique data to new location
For i = 1 To UnqArray1.Count
rCell(i, 1) = UnqArray1(i)
Next
'Move Split Array into a new array
Set rTable2 = rCell
rng2() = rTable2.Value
'Filter into unique items
On Error Resume Next
For Each b In rng2
UnqArray2.Add b, b
Next
'Clear location
rCell.Clear
'Add new array to location
For i = 1 To UnqArray2.Count
rCell(i, 1) = UnqArray2(i)
Next
'Find the end of the category list
lastrow = Worksheets("CatMatch").Range("Q100000").End(xlUp).Row
'Sort alphabetically
Worksheets("CatMatch").Range("Q1:Q" & lastrow).Sort key1:=Range("Q1"), order1:=xlAscending, Header:=xlNo
'Copy it to CatMatch
Worksheets("CatMatch").Range("Q1:Q" & lastrow).Copy Destination:=Worksheets("CatMatch").Range("B15")
MsgBox "Completed"
End Sub

I can't reproduce your error, but you are almost certainly incorrect that it runs okay without the MsgBox. The problem is that the problem with your code is being hidden by On Error Resume Next in the fragment:
'Filter into unique items
On Error Resume Next
For Each b In rng2
UnqArray2.Add b, b
Next
Two comments:
1) Why not use the RemoveDuplicates method if that is what you are trying to do?
2) Your code is using the fact that a collection throws an error if you try to add a duplicate key. This is a valid use of On Error Resume Next -- but only if you turn it off when you are done adding keys to the collection. Something like:
On Error Resume Next
For Each b In rng2
UnqArray2.Add b, b
Next
On Error GoTo 0
A good habit to get into is to consider On Error Resume Next and On Error GoTo 0 as defining a block of code, perhaps even indenting the code inside the block as I did above. An even better habit is to not assume that only 1 type of error can happen. The above code is expecting that error 457 might arise (this is the error number corresponding to trying to add a duplicate key -- you need to search documentation to find it, or just run your code without the error handling and see how it crashes). Anything else indicates some other problem. To be maximally safe you can do something like:
On Error Resume Next
For Each b In rng2
UnqArray2.Add b, b
If Err.Number > 0 And Err.Number <> 457 Then
MsgBox "Unhandled error: " & Err.Number
Exit Sub
End If
Next
On Error GoTo 0
Doing this won't solve your problem, but should make your actual problem more apparent.

Related

Skip code if no error occurs by using an "if statement" in VBA

I need some input, since I'm quite new to VBA.
I have a part of a code that is looking for a file in three different path location. The code tries path 1, if error, it will continue to the next path (that's perfect)
My problem is, if the file path is working for code "test 2" and "test 3", It will always run until last one (i.e. "test 3") instead of jumping to next part of code. If the location path works for example for test 1 or test 2 location, I don't need to run the following lines after.
How can I make my code skip that part?
'Test 1 location path
On Error GoTo Err1:
sFldr = "Path1"
Err1:
Resume Next
'Test 2 location path
On Error GoTo Err2:
sFldr = "Path2"
Err2:
Resume Next
'Test 3 location path
On Error GoTo Err3:
sFldr = "Path3"
Err3:
Resume Next
'next part of big code
more code here
If I understand correctly, you need to progressively try values for (in this contrived example) sFldr, and the operation might error. Instead of using On Error Goto, just test the Err object for an error directly:
On Error Resume Next
sFldr = "Path1"
If Err.Number <> 0 Then
Err.Clear
sFldr = "Path2"
If Err.Number <> 0 Then
Err.Clear
sFldr = "Path3"
End If
End If
On Error GoTo 0 'Or resume your normal error handling.
'next part of big code
Call it like this:
If GetFldrValue = vbNullString Then
'whatever you need to do if all 3 fail.
End If
'next part of big code
Another option is to extract the whole thing into its own function (which might not be a bad idea - the comment "next part of big code" indicates that the routine might be doing too much). If you do that, you can just turn error handling off entirely and return the first valid value found:
Function GetFolderValue() As String
On Error Resume Next
GetFolderValue = "Path1"
If Err.Number <> 0 Then Exit Function
GetFolderValue = "Path2"
If Err.Number <> 0 Then Exit Function
GetFolderValue = "Path3"
End Function
One Procedure = One Error Handler.
Simple as that.
Make sure the error-handling subroutine only ever runs in an error state.
I can't really give you a more concrete answer than that, because the code you're showing us does literally nothing; assigning a string literal to a string variable is never going to raise an error, ...and a workflow that jumps around up and down from one line to another in the "normal" execution path isn't sane - you need to restructure things. I'd love to help, but I've no idea what your code does.
To put it shortly, you should have small procedures that look like this:
Private Sub DoSomething()
On Error GoTo CleanFail
'procedure code here
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
'error-handling code here
Resume CleanExit
End Sub
Option 1: Wrap in Function
It would be best to wrap this in a function that is responsible for retrieving the value of sFldr.
Option 2: GoTo Statement - not recommended
Perhaps add a GoTo if the value of sFldr is not null
'Test 1 location path
On Error GoTo Err1:
sFldr = "Path1"
If(sFldr <> "") Then Goto ContinueFunc
Err1:
Resume Next
'Test 2 location path
On Error GoTo Err2:
sFldr = "Path2"
If(sFldr <> "") Then Goto ContinueFunc

VBA Array Error 2007 if statement

Probably its a simple and common question but I did not find anything in google.
I store some data in array but sometimes this data has error value, in this case xlErrDiv0 2007 #DIV/0!
I use this array in a loop so i need to check if I loop through this incorrect value.
I tried:
If vRangeShift(1, i) <> CVErr(xlErrDiv0) Then
End if
If vRangeShift(1, i) = "Error 2007" Then
and some others options but I always receive type mismatch error.
vRangeShift is Variant type. The problems occurs only when I check this incorrect array element.
This should do the trick:
If IsError(vRangeShift(1, i)) Then
One approach among several would be to catch the error and handle it with some code further down.
On error goto HandleMyError
'do something with vRangeShift(1, i)
'... more code here ....'
Cont:
'finish out sub
Exit Sub 'Have this here so if there isn't an error, you can skip over
'the error handler
HandleMyError:
'if doing something with vRangeShift(1,i) produces an error, you
'you will end up here where you can handle the error as you see fit
'once you've satisfactorily handled the error, you can return to
'that part of your code where you want the sub to continue
Goto Cont
End Sub

Removing object required message with cancel msgbox but does not perform delete action

Now i can delete but whenever i fail to highlight a cell to delete it gives me an object required . i tried removing the error message with a cancel message box but it does not perform the delete action. Would need help to correct my mistake!
Sub deletetry2()
Dim R As Range
On Error Resume Next
Set R = Application.InputBox("Select cells To be deleted", , , , , , , 8)
On Error GoTo 0
If TypeName(rng) <> "Range" Then
MsgBox "Cancelled", vbInformation
Exit Sub
Else
R.delete
End If
End Sub
Application.InputBox is more flexible than InputBox. It's last parameter is a type parameter, with type 8 corresponding to selecting a range with the mouse.
Sample code:
Sub DeleteCells()
Dim R As Range
On Error GoTo err_handler 'if user hits "Cancel"
Set R = Application.InputBox("Select cells To be deleted", , Selection.Address, , , , , 8)
R.Delete
err_handler:
'any needed error handling code here
End Sub
On edit: I modified the code to incorporate brettdj's excellent suggestion to include the selection as the default range. I also added some error trapping to catch the case that the user clicks "Cancel" on the input box. In this case the error handling is to just exit the function, though certainly more involved error handling could be appropriate for your specific case.
A final thought -- make sure that you really want to delete the cells. That is sometimes a problematic move since it causes other cells to shift around to fill the vacuum. In many cases Clear or ClearContents can delete what you want to delete without effecting other cells.

On Error works in first and doesn't work in second instance. Bug?

I have a very strange problem here. Here's the code:
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
'first loop -------------------------------------
On Error GoTo reqLangVisible
i = 1
'Loop until ccCROENG's are all hidden and then go to reqLangVisible.
Do
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = True 'hides all CCs
i = i + 1
Loop
reqLangVisible:
'second loop -------------------------------------
On Error GoTo langOut
i = 1
'Loop until ccreqLang's are all visible and then go to langOut.
Do
ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)(i) _
.Range.Font.Hidden = False 'activates reqLang CCs
i = i + 1
Loop ' CAN'T GET OUT -----------------------------------
Case "CROENG"
i = 1
'Loop until ccCROENG's are all visible and then go to langOut.
Do
On Error GoTo langOut
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = False 'Shows all CCs
i = i + 1
Loop
End Select
langOut:
MsgBox "Success!" '------- yeah, not really.
Stop
I hope it's clear enough what it's trying to do (at least programming-wise). I have multiple ContentControls(CCs) with same titles and tags. The problem I end up with is marked with CAN'T GET OUT, because, you guessed it - I can't get of this second loop! I end up with the Out of range error because it runs out of CCs.
What's even weirder is that it actually did get out of the first loop which has the exact same On Error statement, thought pointing to a different section.
Is it me, or did I just - however unlikely - run onto a bug in VBA?
In any case, is there a solution or at least a workaround?
Typically you only use error handling for dealing with unexpected or unpredictable situations, such as not being able to access a drive, or finding you have no network access.
Error handling is not intended as a substitute for reasonable checks which could otherwise be done. i.e. collections have a Count property which you can use when looping over their items, so avoiding any error caused by trying to access Item(n+1) when there are only n items (and here you know n from Count). Alternatively, use a For Each loop.
Here's some sample code demonstrating use of two methods for looping over your controls:
Sub Tester()
Dim cc1 As ContentControls, cc2 As ContentControls
Dim c, i As Long
With ActiveDocument
Set cc1 = .SelectContentControlsByTag("tbTag")
Set cc2 = .SelectContentControlsByTitle("tbTitle")
End With
Debug.Print "cc1 has " & cc1.Count
Debug.Print "cc2 has " & cc2.Count
'use the Count property
For i = 1 To cc1.Count
Set c = cc1(i)
c.Range.Font.Hidden = True
Next i
'use a For Each loop
For Each c In cc2
c.Range.Font.Hidden = False
Next c
End Sub
This is the type of scenario for which this type of flow control is designed.
Applied to your original code:
Sub Tester2()
Dim reqLang, cc As ContentControls, c
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, True
Set cc = ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)
SetTextHidden cc, False
Case "CROENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, False
End Select
MsgBox "Success!" '-- yeah really
End Sub
Sub SetTextHidden(cc As ContentControls, MakeHidden As Boolean)
Dim c
For Each c In cc
c.Range.Font.Hidden = MakeHidden
Next c
End Sub
So if you've read my comment, and to formally answer your question, it is not a bug.
You just need to use Error Handling Routines correctly.
What you're trying to do is somewhat like below. HTH.
Select Case reqlang
Case "CRO", "ENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
Case "CROENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
End Select
MsgBox "Success"
But as the link suggest, you need to handle errors and not simply disregard them. Try cheking on the actual error and find a way to correct it or avoid it.
You'll be surprise that you won't even be needing the Error Handling Routine.

Excel VBA: On Error Goto statement not working inside For-Loop

I'm trying to cycle through a table in excel. The first three columns of this table have text headings, the rest of them have dates as headings. I want to assign those dates, sequentially, to a Date-type variable, and then perform some operations based on the date
To do this I am using a foreach loop on myTable.ListColumns. Since the first three columns do not have date headers, I have tried to set the loop up so that, if there is an error assigning the header string to the date-type variable, the loop goes straight to the next column
This seems to work for the first column. However, when the second column's header is 'assigned' to the date-type variable, the macro encounters an error even though it is within an error-handling block
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo NextCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
NextCol:
On Error GoTo 0
Next myCol
To reiterate, the error is thrown on the second round of the loop, at the statement
myDate = CDate(myCol.Name)
Can anyone explain why the On Error statement stops working?
With the code as shown, you're actually still considered to be within the error handling routine when you strike the next statement.
That means that subsequent error handlers are not allowed until you resume from the current one.
A better architecture would be:
Dim myCol As ListColumn
For Each myCol In myTable.ListColumns
On Error GoTo ErrCol
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
' MORE CODE HERE '
NextCol:
Next myCol
Exit Sub ' or something '
ErrCol:
Resume NextCol
This clearly delineates error handling from regular code and ensures that the currently executing error handler finishes before you try to set up another handler.
This site has a good description of the problem:
Error Handling Blocks And On Error Goto
An error handling block, also called an error handler, is a section of code to which execution is tranferred via a On Error Goto <label>: statement. This code should be designed either to fix the problem and resume execution in the main code block or to terminate execution of the procedure. You can't use the On Error Goto <label>: statement merely skip over lines. For example, the following code will not work properly:
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo Err2:
Debug.Print 1 / 0
' more code
Err2:
When the first error is raised, execution transfers to the line following Err1:. The error hander is still active when the second error occurs, and therefore the second error is not trapped by the On Error statement.
You need to add resume of some sorts in your error handling code to indicate the error handling is over. Otherwise, the first error handler is still active and you are never "resolved."
See http://www.cpearson.com/excel/errorhandling.htm (specifically the heading "Error Handling Blocks And On Error Goto" and following section)
Follow-up to paxdiablo's accepted answer. This is possible, allowing two error traps in the same sub, one after the other :
Public Sub test()
On Error GoTo Err1:
Debug.Print 1 / 0
' more code
Err1:
On Error GoTo -1 ' clears the active error handler
On Error GoTo Err2: ' .. so we can set up another
Debug.Print 1 / 0
' more code
Err2:
MsgBox "Got here safely"
End Sub
Using On Error GoTo -1 cancels the active error handler and allows another to be set up (and err.clear doesn't do this!). Whether this is a good idea or not is left as an exercise for the reader, but it works!
Clearing all property settings of the Err object is not the same as resetting the error handler.
Try this:
Sub TestErr()
Dim i As Integer
Dim x As Double
On Error GoTo NextLoop
For i = 1 To 2
10: x = i / 0
NextLoop:
If Err <> 0 Then
Err.Clear
Debug.Print "Cleared i=" & i
End If
Next
End Sub
You'll notice the just like the OP, it will catch the error properly when i =1 but it will fail on line 10 when i = 2, even though we used Err.Clear
Dim ws As worksheets
For Each myCol In myTable.ListColumns
On Error GoTo endbit
Dim myDate As Date
myDate = CDate(myCol.Name)
On Error GoTo 0
'MORE CODE HERE
endbit:
Resume NextCol
NextCol:
' Next myCol
Exit Sub ' or something '