Identify empty "Range" after it is deleted - excel

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

Related

Excel VBA named range scope, and intersect function

I have a sheet where user picks a value from a dropdown. Then I search for that value on a different sheet, and return the that cell's address. The address is fed to a function that looks through each named range and looks for an intersect. I have thrown in some "debug" info on the function just to see if it's firing, and it's not. I have 3 sheets that it could potentially look through, so i have to be careful to not throw a 1004 error looking for an intersect on different sheets. I'm at a loss here. I'm sure it's something dumb, but I can't find it. Any help is appreciated.
For i = 1 To EndWRow
For j = 1 To EndWColumn
If woodSearch = Worksheets("Woods").Cells(i, j).Value Then
Worksheets("Woods").Activate
x = ActiveSheet.Name
y = Worksheets("Woods").Cells(i, j).Address
Set wMatchCell = Worksheets("Woods").Cells(i, j)
If IsNamedRange(wMatchCell, woodRangeName) Then
MsgBox "Range Name:= " & woodRangeName.Name & Chr(10) & _
"Range RefersTo:= " & woodRangeName
woodRangeNameString = woodRangeName.Name
'Worksheets("Woods").Range(woodRangeNameString).Copy
'Worksheets("Bag 1").Range("B2:H23").PasteSpecial xlPasteValuesAndNumberFormats
'Worksheets("Bag 1").Range("B2:H23").PasteSpecial xlPasteFormats
'Worksheets("Bag 1").Range("B2:H23").PasteSpecial xlPasteColumnWidths
Else
MsgBox "Invalid Selection. Sheet name is: " & x & " and selected address is: " & y
End If
Exit For
End If
Next j
Next i
Find named range function:
Function IsNamedRange(ByVal Target As Range, ByRef NamedRange As Name) As Boolean
Dim nm As Name
Dim i As Integer
i = 7
With ThisWorkbook.Sheets(Target.Parent.Name)
For Each nm In .Names
Sheets("User Entry").Range("B" & i).Value = nm
If Not Application.Intersect(Target, Range(nm)) Is Nothing Then
IsNamedRange = True
Set NamedRange = nm
Exit Function
End If
i = i + 1
Next nm
End With
End Function
At the very least, I feel like this function should at least print all of the named ranges on my sheet (not really desired but I threw it in for debug) and it doesn't do anything. Long and short is the "else" ends up executing...
You can make some changes to make it more generic. Also updated the method name and returned the name as the function's return value - I'm not sure the Boolean + ByRef is getting you anything more than just testing the return value?
Sub Tester()
Dim nm As Name
Set nm = GetMatchedRange(Selection)
If Not nm Is Nothing Then
Debug.Print nm.Name, nm.RefersToRange.Address
Else
Debug.Print "no name"
End If
End Sub
Function GetMatchedRange(ByVal Target As Range) As Name
Dim nm As Name, i As Integer, rng As Range
'always operate on the parent workbook of Target
For Each nm In Target.Worksheet.Parent.Names
'Sheets("User Entry").Range("B" & i).Value = nm
Set rng = Nothing
On Error Resume Next
Set rng = nm.RefersToRange 'not all names refer to ranges...
On Error GoTo 0
If Not rng Is Nothing Then
'does this name refer to a range on the same sheet as Target?
If rng.Parent.Name = Target.Parent.Name Then
If Not Application.Intersect(Target, rng) Is Nothing Then
Set GetMatchedRange = nm
Exit Function
End If
End If
End If
i = i + 1
Next nm
End Function

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

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

Is it possible to get the name of a range that the active cell is in?

Scenario: Range is named "Dog" and the named range Dog refers to A1:D4. The active cell is in cell B3, which is within the named range.
Is it possible to get the name of the named range that the active cell is in? ie return the name "Dog"?
Perhaps something like the following, which tests the Intersection of the ActiveCell and each named range.
The On Error Resume Next...On Error GoTo 0 is necessary since Intersect will fail when the ActiveCell and the named range are on different sheets, or if n is not a named range but if it refers to a constant or formula, for example.
Sub test()
Dim n As Name
For Each n In ActiveWorkbook.Names
Dim rng As Range
Set rng = Nothing
On Error Resume Next
Set rng = Intersect(ActiveCell, n.RefersToRange)
On Error GoTo 0
If Not rng Is Nothing Then
Debug.Print n.Name
End If
Next
End Sub
This should be a more robust way...
Sub Test()
MsgBox NamesUsedBy(ActiveCell)
End Sub
Function NamesUsedBy(r As Range)
Dim s$, n
On Error Resume Next
For Each n In ThisWorkbook.Names
If Intersect(r, Evaluate(Mid(n, 2))).Row Then
If Err = 0 Then s = s & ", " & n.Name
End If
Err.Clear
Next
NamesUsedBy = Mid(s, 3)
End Function
There is probably a more elegant way of doing this, but this should work.
Sub test()
Dim currentrange As Range
Dim r As Variant
Set currentrange = ActiveCell
For Each r In ThisWorkbook.Names
If Not Application.Intersect(currentrange, Range(Right(r, InStr(1, r, "$")))) Is Nothing Then
Debug.Print r.Name
End If
Next r
End Sub

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).

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.

Resources