Is there a 'On Error Resume Next' function that also logs errors in VBA? - excel

I have code which looks through an Excel table.
Sub ErrorCheck()
Dim ErrColl As New Collection
Dim NameColl As New Collection
Worksheets(WorksheetName).Select
Worksheets(WorksheetName).Range("B5").Select
Do Until IsEmpty(ActiveCell)
On Error Goto eh
NameColl.Add ActiveCell.Value
ActiveCell.Offset(0, 1).Select
Loop
eh:
ErrColl.Add ActiveCell.Value
End Sub
The above will stop once a duplicate occurs. I don't want the code to stop when a duplicate occurs, because I need all the duplicates to be shown in msgbox/logged onto a file. 'Resume next' will give me the right answer with no duplicates, but will not show the where the duplicates are. 'GoTo' will only show the first error. Is there another way to do this?

You can always combine ´On Error Resume Next´ with a check of the Error code. The following (very silly) code should illustrate how this works:
On Error Resume Next
Dim i As Integer
Dim m As Integer
Dim n As Double
For i = 1 To 10
m = i Mod 3
n = i / m
If Err.Number > 0 Then
MsgBox ("Handle error here")
Err.Number = 0
Else
MsgBox ("n: " + Str(n))
End If
Next
What this is doing is to test for the Error code on the line after the error is likely to occur. If there was no error (Err.Number = 0), the code continues with the Else. If an error has been raised (in this case Err.Number = 11 - division by zero when i is 3, 6 and 9), then you can handle the error within the If and the code continues after the End If. Notice that you need to reset the Err.Number back to 0!

I wouldn't select stuff so much because it will slow your code down.
Anyway, here's something that might be of use. Change the code where necessary.
Sub ErrorCheck()
Dim rCell As Range
Dim lRow As Long
Dim rCheck
With Worksheets(1) 'change to suit
Set rCell = .Cells(5, 2)
Set rCheck = rCell
lRow = 1
Do Until rCell(lRow).Value = vbNullString
Set rCheck = Union(rCheck, rCell.Offset(lRow))
With rCell.Offset(lRow)
If WorksheetFunction.CountIf(rCheck, .Value) > 1 Then
Debug.Print .Address & vbTab & .Value 'using the Immediate Window as an example
End If
End With
lRow = lRow + 1
Loop
End With
Set rCell = Nothing
Set rCheck = Nothing
End Sub

Related

Getting error 'object variable or With block variable not set' when trying to run sub

I did not create the code but am trying to troubleshoot an excel file and the original author is not available (layed off from company and not willing to help).
The following line is generating the error, 'object variable or With block variable not set'
Private Sub Workbook_Open()
Sheet1.Starttimer
End Sub
I looked at Sheet1 code and found the below, so I'm not sure what the problem is:
Sub Starttimer()
Application.DisplayAlerts = False
If Not Sheet4.ListObjects(1).DataBodyRange Is Nothing Then
Sheet4.ListObjects(1).DataBodyRange.Rows.Delete
End If
ActiveWorkbook.RefreshAll
Application.Calculate
SetProductionZeros
ActiveWorkbook.Save
ThisWorkbook.Close
End Sub
UPDATE
After setting the debug to break on all errors, the line that causes the error appears to be "r = Sheet4.ListObjects(1).DataBodyRange.Rows.Count" from the sub below:
Sub SetProductionZeros()
Dim tb1 As ListObject
Dim x As Long
Dim y As Long
Dim r As Long
Dim c As Long
'Set path for Table variable'
Set tb1 = Sheet4.ListObjects(1)
Sheet4.Activate
r = Sheet4.ListObjects(1).DataBodyRange.Rows.Count
c = Sheet4.ListObjects(1).DataBodyRange.Columns.Count
'Loop Through Each DataBody Row in Table
For y = 1 To r
'Loop Through Each Column in Table
For x = 1 To c
If IsEmpty(Sheet4.ListObjects(1).DataBodyRange.Cells(y, x)) Then Sheet4.ListObjects(1).DataBodyRange.Cells(y, x) = 0
Next x
Next y
Sheet4.Columns(5).EntireColumn.Delete
Dim lastrow As Long, lastcol As Long, thiscol As Long
Dim totalrow As Long, totalcol As Long, thisrow As Long
totalrow = 7 + Sheet4.ListObjects(1).Range.Rows.Count
totalcol = 2 + Sheet4.ListObjects(1).Range.Columns.Count
On Error GoTo Errorcatch
'lastrow = Cells(Rows.Count, 1).End(xlUp).row
'lastcol = Cells(1, Columns.Count).End(xlToLeft).Column
Sheet4.Cells(totalrow, 3).Value = "Total"
For thiscol = 5 To totalcol - 1
Sheet4.Cells(totalrow, thiscol).Select
ActiveCell.Value = WorksheetFunction.Sum(Sheet4.Range(Sheet4.Cells(1, ActiveCell.Column), ActiveCell))
Next
Sheet4.Rows(totalrow).Font.Bold = True
Sheet4.Cells(7, totalcol).Value = "Total"
For thisrow = 8 To totalrow
Sheet4.Cells(thisrow, totalcol).Select
ActiveCell.Value = WorksheetFunction.Sum(Sheet4.Range(Sheet4.Cells(ActiveCell.row, 5), ActiveCell))
Next
Sheet4.Columns(totalcol).Font.Bold = True
'Sheet4.Columns(2).HorizontalAlignment = xleft
For y = totalrow To 8 Step -1
If Sheet4.Cells(y, 2) = "T" And Sheet4.Cells(y, totalcol).Value = 0 Then
Sheet4.Rows(y).EntireRow.Delete
End If
Next
Exit Sub
Errorcatch:
MsgBox Err.Description
End Sub
Follow the logic:
When you open the workbook, you call Sheet1.StartTimer
Sheet1.StartTimer includes
If Not Sheet4.ListObjects(1).DataBodyRange Is Nothing Then
Sheet4.ListObjects(1).DataBodyRange.Rows.Delete
End If
At this point Sheet4.ListObjects(1).DataBodyRange will be Nothing (because you deleted all its rows)
Then you call SetProductionZeros
SetProductionZeros includes r = Sheet4.ListObjects(1).DataBodyRange.Rows.Count
But because Sheet4.ListObjects(1).DataBodyRange is Nothing this throws an error. (Same applies to .Columns.Count)
You can wrap references to DataBodyRange in
If Not Sheet4.ListObjects(1).DataBodyRange Is Nothing Then
' ...
End If
but you need to consider what you want to achieve when there are no rows in Sheet4.ListObjects(1)
This error seems to indicate that you are assigning an object to r without set. Nothing is an object. So in your case you are likely getting Nothing from Sheet4.ListObjects(1).DataBodyRange.Rows.Count. After Set tb1 = Sheet4.ListObjects(1), verify that tb1 is not nothing.
FYI, For code clarity, you should be using r = tb1.DataBodyRange.Rows.Count (same for c =).

Display cell address and message box on error and exit the Macro, if not, continue

I want to first check my Range for #NA errors and then display the cell addresses containing the error before quitting the Macro. This is what I've done so far.
Now, if there are errors present, I'd like to display a MsgBox warning the user of the error and stop the rest of the program from executing, if however there are none then I'd like for it to move on to the rest of the program
Check for NA error:
For Each c In myRange
If IsError(c) = True Then
Debug.Print c.Address
End If
Next c
MsgBox "Check for errors and run gain"
Exit Sub
'continuation of the program
This one will write all the addresses of the errors in a string and will display them after the code runs:
Sub TestMe()
Dim myRange As Range
Dim myCell As Range
Dim errorList As String
Set myRange = Worksheets(1).Range("A1:C10")
For Each myCell In myRange
If IsError(myCell) Then
errorList = errorList & vbCrLf & myCell.Address
End If
Next
If Len(errorList) > 0 Then
MsgBox errorList
Exit Sub
End If
End Sub
AFter the loop, there is a check for the 1Len(errorList) and if it is bigger than 0, it shows the MsgBox and exits the sub.
I think this will do the trick:
Dim errorArray()
Dim i As Integer
Dim checkArray As Integer
Dim errorString As String
For Each c In myRange
If IsError(c) = True Then
ReDim Preserve errorArray(i)
errorArray(i) = c.Address
i = i + 1
End If
Next c
On Error Resume Next
checkArray = UBound(errorArray)
If Err = 0 Then
errorString = "An error(s) occured in following cell(s):" & Chr(10)
For i = 0 To UBound(errorArray)
errorString = errorString & errorArray(i) & Chr(10)
Next
MsgBox errorString
Exit Sub
End If
Err.Clear
On Error GoTo 0
As per my comment you could also try to use SpecialCells to avoid any iteration:
Sub test()
Dim rng As Range
With Sheet1 'Change according to your sheets CodeName
'Setup your range to check for errors
Set rng = .Range("A1:C4")
'Check if any errors exist and act if they do
If .Evaluate("SUM(IF(ISERROR(" & rng.Address & "),1))") > 0 Then
MsgBox "Still errors in " & rng.SpecialCells(-4123, 16).Address(False, False)
Exit Sub
End If
End With
End Sub
If your cells are not the result of formulas but constants instead, please change SpecialCells(-4123, 16) to SpecialCells(2, 16).

WorksheetFunction.Vlookup returns error in a For-Each loop

I am trying to code vlookup using WorksheetFunction to perform as usual vlookup in Excel (dynamic cell that is to be searched and dynamic cells to input results).
Sub vlookupFunction()
Dim cl As Range
Dim searchManagersRange As Range
Dim rangeToSearchManagers As Range
Dim lastRow As Long
lastRow = ThisWorkbook.Sheets(1).Cells(Rows.Count, 1).End(xlUp).Row
Set rangeToSearchManagers = ThisWorkbook.Sheets(1).Range("A2:A" & lastRow)
Set searchManagersRange = ThisWorkbook.Sheets(3).UsedRange
For Each cl In rangeToSearchManagers
On Error GoTo managerNotFound
ThisWorkbook.Sheets(1).Range(cl.Offset(0, 16).Address) = WorksheetFunction.VLookup(cl, searchManagersRange, 2, 0)
Next cl
managerNotFound:
cl.Offset(0, 16).Value = "#N/A"
Resume Next
End Sub
Code works fine and completes the search, but in the end it returns object variable not set...error, as cl is "Nothing" in the end.
Use Application instead of Worksheetfunction and you can "trap" the error without "raising" it, and remove your messy On Error/Resume statements.
Read eg this for more info.
For Each cl In rangeToSearchManagers
cl.Offset(0, 16).Value = Application.VLookup(cl, searchManagersRange, 2, 0)
Next cl
End Sub
You could avoid Loop
Option Explicit
Sub test()
With ThisWorkbook.Sheets(1)
.Range("Q2:Q" & .Cells(.Rows.Count, "A").End(xlUp).Row).Formula = "=IFERROR(VLOOKUP(A2," & ThisWorkbook.Sheets(3).Name & "!" & ThisWorkbook.Sheets(3).UsedRange.Address & ",2,0),"""")"
End With
End Sub

Using a VBA Try/Except Equivalent for If/Else

I am trying to run through some spreadsheet range and use a try/except in order to build an if/else statement. The reason I am doing this is because IsNumeric() is not working for me so I am trying to do something like this (try except formatting from python)
Dim Temp as Integer
Dim Myrange as Range
Dim Myrow as Range
Set Myrange = Range("A1","A1000")
For Each Myrow in Myrange.Row
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For 'To escape the loop at the end of the filled cells
Else
Try:
Temp = (Myrow.Value() - 0) 'This causes a #VALUE! error when the Myrow.Value is not a number.
Except:
Range("B" & Myrow.Row).Value = Temp 'this sets the value of the rightmost cell to whatever current value of Temp is.
I have also tried some other error catching but can't seem to get it in VBA.
For Each Myrow In Myrange.Rows
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For
Else
On Error Resume Next
Temp = Myrow.Value() - 0
If Err.Number = 0 Then
Range("A" & Myrow.Row).Value = ""
ElseIf Err.Number <> 0 Then
Range("B" & Myrow.Row) = Temp
End If
End If
Next Myrow
I am really just looking to run down the list, see the first number, set value of B0:Bn1 = Temp, when An is hit (new number), The value of Temp changes to temp2 and then cells Bn1+1 -> Bn2-1 is temp2 until a new number is found etc.
in the worksheet I can do it fine with dragging down formula =(A1-0) to see the error message for those that are not numeric but for some reason I can't code it.
Solved this using advice of #MathieuGuindon by using variant type and testing isnumeric on that. Solution code:
Dim Myrange As Range
Dim Myrow As Range
Dim Temp As Variant
Dim NextTemp As Variant
Set Myrange = Selection
For Each Myrow In Myrange.Rows
NextTemp = Range("A" & Myrow.Row).Value
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For
ElseIf IsNumeric(NextTemp) Then
Temp = NextTemp
Range("A" & Myrow.Row).Value = ""
Else
Range("B" & Myrow.Row).Value = Temp
End If
Next Myrow
A bit of simplification, and picking up on Mathieu's comments, try this. Not sure what you're doing though so may no be quite right.
Sub x()
Dim Temp As Variant
Dim Myrange As Range
Dim Myrow As Range
Set Myrange = Range("A1", "A1000")
For Each Myrow In Myrange
If Not IsEmpty(Myrow) Then
Temp = Myrow.Value - 0
If IsNumeric(Temp) Then
Myrow.Value = vbNullString
Else
Myrow.Offset(, 1).Value = Temp
End If
End If
Next Myrow
End Sub
One way is to have a dedicated error handler at the end of your sub, and check the error code (13 for Type Mismatch):
Option Explicit
Public Sub EnumerateValues()
On Error GoTo err_handle
Dim Temp As Integer
Dim Myrange As Range
Dim Myrow As Range
Dim myNumber As Double ' Int? Long?
Set Myrange = Range("A1", "A1000")
For Each Myrow In Myrange.Rows
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For ' to escape loop at end of filled cells
Else
myNumber = CDbl(Myrow.Value())
Debug.Print myNumber
End If
' use label, since VBA doesn't support Continue in loop.
loop_continue:
Next Myrow
exit_me:
Exit Sub
err_handle:
Select Case Err.Number
Case 13 ' Type Mismatch
GoTo loop_continue
Case Else
MsgBox Err.Description, vbOKOnly + vbCritical, Err.Number
GoTo exit_me
End Select
End Sub
This way, if we encounter a value for which CDbl (or the equivalent function) fails, we just continue on to the next row.
While the first example contains Try: and Except: as labels, they provide no error control. Try/Except are vb.net error control methods, not vba.
It's unclear whether you might have text that looks like numbers in column A. If the Temp = (Myrow.Value() - 0) is only meant to determine whether the value in column A is a number and not used as a conversion then SpecialCells can quickly find the numbers in column A.
dim rng as range
on error resume next
'locate typed numbers in column A
set rng = Range("A:A").SpecialCells(xlCellTypeConstants, xlNumbers)
on error goto 0
If not rng is nothing then
rng = vbNullString
End If
on error resume next
'locate text values in column A
set rng = Range("A:A").SpecialCells(xlCellTypeConstants, xlTextValues)
on error goto 0
If not rng is nothing then
rng.Offset(0, 1) = rng.Value
End If
You can also use xlCellTypeFormulas to return numbers or text returned by formulas.

Identify empty "Range" after it is deleted

I work with Ranges and sometimes all cells in a given object are deleted (Range.Cells.Delete)
I've played around and find these:
after deleting all cells, type of the variable is still Range and it Is Not Nothing
calling any of it's members results in Error "Object required"
Here is my small sub to test it:
Sub test()
Dim r As Range
Debug.Print r Is Nothing ' -> true
Set r = ActiveSheet.Range("a2")
Debug.Print TypeName(r) ' -> Range
r.Cells.Delete shift:=xlShiftUp
On Error GoTo ErrHandler
Debug.Print r Is Nothing ' -> False
Debug.Print TypeName(r) ' -> Range
Debug.Print r.Address ' -> "Err: Object required"
Debug.Print r.Cells.Count ' -> "Err: Object required"
Exit Sub
ErrHandler:
MsgBox "Err: " & Err.Description
Resume Next
End Sub
My question is: how can I determine (hopefully without error handling) whether an object is an empty Range?
Shortest option I've found so far:
Function IsEmptyRange(testRange As Range) As Boolean
If TypeName(testRange) <> "Range" Then
IsEmptyRange = False
Else
On Error GoTo EmptyRange
IsEmptyRange = testRange.Cells.Count = 0
If False Then
EmptyRange:
Err.Clear
IsEmptyRange = True
End If
End If
End Function
There's an interesting discussion about this on Daily Dose of Excel pointing to a similar Stack Overflow question. The DDoE doesn't give a 'solution' but the discussion is illuminating regarding 'expected behaviour'. The SO post does (by #DougGlancy) but it is just a version of your code (perhaps slightly better because no If False Then... construct required).
IMO the best practice here is to remember to set your reference variables to Nothing if, and when, you can. E.g.
Sub test()
Dim r As Range
Debug.Print r Is Nothing ' -> true
Set r = ActiveSheet.Range("a2")
Debug.Print TypeName(r) ' -> Range
r.Cells.Delete shift:=xlShiftUp
Set r = Nothing ' -> end of problem
On Error GoTo ErrHandler
Debug.Print r Is Nothing ' -> True
If Not r Is Nothing Then
Debug.Print TypeName(r) ' -> Range
Debug.Print r.Address ' -> "Err: Object required"
Debug.Print r.Cells.Count ' -> "Err: Object required"
End If
Exit Sub
ErrHandler:
MsgBox "Err: " & Err.Description
Resume Next
End Sub
The tendency is probably to let scope deal with the clean-up (i.e. r will go out of scope once the Sub is complete) but it is better to do the clean-up yourself.
In my case I don't have any loop, therefore For ... Step -1, as Nigel Heffernan says, is not a solution.
With this problem the correct solution will depend on the context.
This is mine:
Sub test()
'Mi context: from C2 all the cells are empty
Dim Rng As Range
Set Rng = ActiveSheet.Range("A2:B2")
'trim all cells in Rng
Rng = Application.Trim(Rng.Value)
'I use CountIf instead xlCellTypeBlanks because it
'would give an error if there are no empty cells
If WorksheetFunction.CountIf(Rng, "") > 0 Then
'delete all empty cells in Rng
Rng.SpecialCells(xlCellTypeBlanks).Delete Shift:=xlToLeft
End If
'Assign Rng again
Set Rng = ActiveSheet.Range("A2:B2")
'and check again if all cells are empty
If WorksheetFunction.CountIf(Rng, "") = Rng.Cells.Count Then
MsgBox """A2:B2"" range have been deleted"
Else
'do something
End If
End Sub
In other contexts you can store the initial values ​​of Rng and, after deleting cells in the range, compare with the newly assigned Rng

Resources