Error 1004: Unable to Get CountIf Property - vba

I'm trying to search a range of named cells to see if there are any cells that contain a number greater than zero. Here is the code I currently have:
Dim YTDclauses As Boolean
Dim ytdrng As Range
Set ytdrng = Worksheets("Sheet1").Range("z1AY:z1BB,z1BG:z1BJ")
'Employer 1
If Sheet1.[z1AG] = "No" And WorksheetFunction.CountIf(ytdrng, ">0") = 0 Then
MsgBox "Works!"
Else
MsgBox "Does Not Work"
End If
I'm getting an error back as "Run-time error '1004': Unable to get the CountIfs property of the WorksheetFunction class". By looking at other questions, I think it might be a syntax error with how I'm setting ytdrng, but I've tried many ways of naming it differently to no avail. Any help is appreciated, thank you!
Note: Sheet1 is named "Main Checklist" - I also tried using that in the setting of ytdrng, but got the same error.

As #ScottCraner has stated, you cannot do a countif on a split range. You can modify the routine slightly to implement a countif by looping over each cell in the range:
Dim YTDclauses As Boolean
Dim ytdrng As Range
Dim SRCountif As Long
Dim cel As Object
Set ytdrng = Worksheets("Sheet1").Range("z1AY:z1BB,z1BG:z1BJ")
SRCountif = 0
For Each cel In ytdrng.Cells
If cel.Value > 0 Then SRCountif = SRCountif + 1
Next
'Employer 1
If Sheet1.[z1AG] = "No" And SRCountif = 0 Then
MsgBox "Works!"
Else
MsgBox "Does Not Work"
End If
(The variable SRCountif is meant to mean SplitRangeCountif)
Note that as it is comparing the value to numeric 0, Exec takes any text as greater than 0, so you may want to adjust the test if there is a chance of any text in your range.

Related

How do I find out why I get an error when writing to an Excel cell with VBA?

I'm still fairly new to VBA and struggling with its limitations (and mine!). Here's my code:
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Variant, RowNum As Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue
On Error Resume Next
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub
When I step through the code, Excel simply exits the sub at the line DataCacheWorksheet.Cells(1, 1).Value = CacheKey, with no error. So, two questions:
What's the bug that's preventing the value from being updated?
Why does Excel ignore my On Error command?
Edit: If I run the line in the IDE's "Immediate" box, I get the error "Run-time error '1004' Application-defined or object-defined error. I get the same error regardless of the value of CacheKey (I tried Empty, 1234 and "Hello").
Edit 2: If I modify the sub so that CacheKey and CacheValue are hardcoded and the reference to DataCache is removed, and then I run the sub standalone it works. So why doesn't it work when called from another function? Is it possible that Excel is locking cells while doing calculations?
Not sure if this applies, but you mentioned you were calling this macro from another function. If you are calling it from a function, depending on how you are calling it, that would explain your problem. For example, a worksheet function entered into a cell cannot modify another cell on the worksheet. And the attempt to do so will result in the macro merely exiting at that point, without throwing a VBA error.
How to work around this depends on specifics you have yet to share. Sometimes, worksheet event code can be useful.
Ok, wasn't about to write an answer, but there are 3 things you should modify in your code:
Found As Range and not As Variant
RowNum As Long in case it's a row after ~32K
To trap errors usually On Error Resume Next won't help you, it will just jump one line of code. You need to handle the error situation.
Modified Code
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Range, RowNum As Long ' < use Long instead of Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then ' check if not found in cache (*Edit 1)
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue ' I assume you have a `Dictionary somewhere
' On Error Resume Next <-- Remove this, not recommended to use
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub

Excel VBA .Find Function changing values in selection

Heya this is probably simple but I cannot figure out what is wrong.
I am trying to do .find for a specific date and change that selection to a user input date.
I have a userform to select a date from a combobox (date1_cbo). The combobox source is linked to dates on a worksheet (Backend). There is a textbox below for writing the new date to change it to (date1_txt). I keep getting an error
object variable or with block variable not set.
I have tried a few options without any luck here is my code:
Dim selection As Range
Dim check As Boolean
'input box validation
check = IsDate(date1_txt.Value)
If check = True Then
'find cell matching combobox
With Worksheets("Backend").Range("A1:A500")
Set selection = .Find(date1_cbo.Value) 'this is the problem
selection.Value = date1_txt.Value
End With
Else
End If
Interestingly .Find returns the range or Nothing. however because the combobox is linked to the cells I am searching through this should never return nothing... I dont understand why the error is occurring.
a variable named as 'selection' is bad coding practice but totally legal. Don't use such names for the sake of clarity.
Error 91 is caused when you are trying to read a property( .value) from null object. your selection variable is null cause date formats on the sheet and combobox are different.
Just convert to date before attempting to find it in sheet.
Set selection = .Find(CDate(date1_cbo.Value)) '/ once again, selection is valid but bad name for variable.
You are using a variable named Selection. VBA uses it as well. Rename your variable to anything else, rewrite your code and it should work. Even Selection1 is quite ok:
Public Sub TestMe()
Dim selection1 As Range
With Worksheets(1).Range("A1:A500")
Set selection1 = .Find("vi")
End With
If Not selection Is Nothing Then
selection1.Value = "some other value"
End If
End Sub
To change multiple values with Find() as here - A1:A10, then some possibility is to do it like this:
Public Sub TestMe()
Dim myRng As Range
With Worksheets(1).Range("A1:A500")
.Range("A1:A10") = "vi"
Set myRng = .Find("vi")
If Not myRng Is Nothing Then
Do Until myRng Is Nothing
myRng.Value = "New value"
Set myRng = .Find("vi")
Loop
End If
End With
End Sub
It is a bit slow, as far as it loops every time and it can be improved, if the range is united and replaced at once.

Find invalid data validation cells

Description:
I have created a custom dropdown data validation list where I can choose among several values. These values on the dropdown list changes as I need (are defined in a worksheet column X).
Problem:
My problem occurs when I choose one of those values, let say Y, from the dropdown list and then I update the data validation by removing the last inserted value (deleted the Y value from column X). By doing this the value Y present in the worksheet is no longer valid so I would like to know if there is a way to obtain a list (array or string) of cells with the invalid data.
What I have done/thought so far:
I have searched in several sites and read similar questions but I cannot find anything usefull. I thought about looping all the cells and check if the value is valid but since I have a huge amount of data I think that it is not the best approach.
Since Excel already mark these invalid data with a red circle maybe it could be possible to get the address of those marked cells?
Thanks in advance!
The correct way to obtain the invalid cells in a worksheet is using Cells.SpecialCells(xlCellTypeAllValidation).
By using some information present in Microsoft KB213773 (Q213773) - "How to create data validation circles for printing in Excel" a similar Sub can be used to loop all invalid cells and then change their values (or mark them to future edit).
Sub CorrectInvalidValues()
Dim data_range As Range
Dim invalid_cell As Range
Dim count As Integer: count = 0
Dim nr_invalid As Integer: nr_invalid = 0
Dim new_value As String
'If an error occurs run the error handler and end the procedure
On Error GoTo errhandler
Set data_range = Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo 0
' Loop through each cell that has data validation and gets the number of invalid cells
For Each invalid_cell In data_range
If Not invalid_cell.Validation.Value Then
nr_invalid = nr_invalid + 1
End If
Next
' Editing each value
For Each invalid_cell In data_range
If Not invalid_cell.Validation.Value Then
count = count + 1
Application.Goto reference:=invalid_cell, Scroll:=True
new_value = Application.InputBox("Please insert a correct value.", "Invalid Data " & count & "/" & nr_invalid)
If Not (new_value = "False") Then
invalid_cell.Interior.ColorIndex = 0
invalid_cell.Value = new_value
Else
invalid_cell.Interior.Color = RGB(255, 0, 0)
invalid_cell.Value = "<PLEASE EDIT>"
End If
End If
Next
Exit Sub
errhandler:
MsgBox "There are no cells with data validation on this sheet."
End Sub

Assignment to constant not allowed

I am getting this error in one of my macros, the code is
Dim rdel1 As Range
Dim rdel2 As Range
Set rdel1 = Sheets("Sheet1").Range(A1:B100)
For Each rdel2 In rdel1.Cells
If rdel2.Value = "No item selected" Then
rdel2.Offset(1, 0).EntireRow.Delete
rdel2.EntireRow.Delete
rdel2.Address = rdel2.Offset(-1, 0) "Error in this line"
End If
Next rdel2
I want to change the address of redel2 by offset(-1,0). I know it dosen't look the right way to write it but I am unable to get the right syntax to change it. Can someone help! Please!
After you execute
rdel2.EntireRow.Delete
rdel2 will be `Nothing' so any attempt to manipulate it will fail.
If it were not Nothing, and referenceing a cell in a row > 1, then
Set rdel2 = rdel2.Offset(-1, 0)
would work.
It's not clear exactly what you want to achieve, but this may get you started
Sub Demo()
Dim rdel1 As Range
Dim rdel2 As Range
Set rdel1 = Sheets("Sheet1").Range("A1:A100")
Dim rw As Long
For rw = rdel1.Rows.Count To 1 Step -1
Set rdel2 = rdel1.Cells(rw, 1)
If rdel2.Value = "No item selected" Then
rdel2.Offset(1, 0).EntireRow.Delete
End If
Next
End Sub
rdel2 is a range and .Offset(-1,0) returns a range, just do rdel2 = rdel2.Offset(-1, 0) if you want to change rdel2.
Although, in your case, the For Each loop will update rdel2 so this line will be useless (unless you are not showing all your code and there is actually more between the problematic line and the Next rdel2 statement)

VBA: What happens to Range objects if user deletes cells?

Suppose I have some module in vba with some variable r of type Range. Suppose that, at some point, I store a Range object there (e.g. the active cell). Now my question: What happens to the value of r if the user deletes the cell (the cell, not only its value)?
I tried to figure this out in VBA, but without success. The result is strange. r is not Nothing, the value of r is reported to be of type Range, but if I try to look at its properties in the debugger window, each property's value is reported as "object required".
How can I, programmatically, determine whether variable r is in this state or not?
Can I do this without generating an error and catching it?
Nice question! I've never thought about this before, but this function will, I think, identify a range that was initialzed - is not Nothing - but is now in the "Object Required" state because its cells were deleted:
Function RangeWasDeclaredAndEntirelyDeleted(r As Range) As Boolean
Dim TestAddress As String
If r Is Nothing Then
Exit Function
End If
On Error Resume Next
TestAddress = r.Address
If Err.Number = 424 Then 'object required
RangeWasDeclaredAndEntirelyDeleted = True
End If
End Function
You can test is like this:
Sub test()
Dim r As Range
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
Set r = ActiveSheet.Range("A1")
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
r.EntireRow.Delete
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
End Sub
I believe that when you use the Set keyword in VBA, it creates a pointer in the background to the worksheet's Range object in the worksheet you specified (each cell being an object in the collection of Cells of the Worksheet for a given Range). When the range is deleted while you are still referencing it in memory, the memory for the object that the Range variable was pointing to has been deallocated.
However, your Range variable most-likely still contains the pointer to the recently removed Range object, which is why it isn't nothing, but whatever it's pointing to doesn't exist anymore, which causes problems when you try to use the variable again.
Check out this code to see what I mean:
Public Sub test2()
Dim r As Excel.Range
Debug.Print ObjPtr(r) ' 0
Set r = ActiveSheet.Range("A1")
Debug.Print ObjPtr(r) ' some address
r.Value = "Hello"
r.Delete
Debug.Print ObjPtr(r) ' same address as before
End Sub
Check out this article for more info about ObjPtr():
http://support.microsoft.com/kb/199824
So while you have a valid address to an object, unfortunately the object doesn't exist anymore since it has been deleted. And it appears that "Is Nothing" just checks for an address in the pointer (which I think VBA believes that the variable is "Set").
As to how to get around this problem, unfortunately I don't see a clean way of doing it at the moment (if anyone does find an elegant way to handle this, please post it!). You can use On Error Resume Next like so:
Public Sub test3()
Dim r As Excel.Range
Debug.Print ObjPtr(r) ' 0
Set r = ActiveSheet.Range("A1")
Debug.Print ObjPtr(r) ' some address
r.Value = "Hello"
r.Delete
Debug.Print ObjPtr(r) ' same address as before
On Error Resume Next
Debug.Print r.Value
If (Err.Number <> 0) Then
Debug.Print "We have a problem here..."; Err.Number; Err.Description
End If
On Error GoTo 0
End Sub
How can I, programmatically, determine whether variable r is in this
state or not?
Can I do this without generating an error and catching it?
No.
To the best of my knowledge, you can't test for this condition reliably: not without raising and catching an error.
Your question has been noticed and discussed elsewhere: Two of the big names in Excel/VBA blogging (Dick Kusleika and Rob Bovey) have looked into it, and you may find something informative in there. But the answer's No.
All in all, a good question with rather worrying answer.
To test if a range object is currently invalid, I use this function:
Public Function InvalidRangeReference(r As Range) As Boolean
On Error Resume Next
If r.Count = 0 Then
InvalidRangeReference = Err
End If
End Function