How do I determine if an Excel range is hidden? - vba

In my code I include a boolean variable in which I want to assign the value of a range's hidden property. i.e. if the range is hidden, the variable should have the value true, and vice versa.
While running the code I get a '1004' Run-time error - Unable to get the Hidden property of the Range class. By this I assume that Hidden property in this case is write-only (correct me if I'm wrong).
Is there a way to determine (in my code, not by watching) whether a range/cell is hidden or not?
I have a class named "minas" and with this sub I am trying to create a collection of minas based on some criteria.
Public mines As Collection
Sub existing_months()
Set mines = New Collection
Dim min As minas
Dim str As String
Dim x As Range
Dim y As Boolean
For i = 1 To 12
Set min = New minas
Set x = Range("A1:A500").Find(i, LookIn:=xlValues, LookAt:=xlWhole)
If x Is Nothing Then GoTo next_iteration:
y = x.Hidden 'does not get the property
Call min.initialize(x, y)
str = min.minas & "/" & min.etos
mines.Add min, str
Debug.Print min.ref_range.Address & " " & min.end_cell
next_iteration:
Next
Set min = Nothing
End Sub

You can say a cell is hidden if it is located on a hidden row or hidden column.Then a range is hidden if all cells in that range are hidden:
Public Function IsHidden(rIn As Range) As Boolean
Dim r As Range
IsHidden = True
For Each r In rIn
If Not r.EntireRow.Hidden Then
If Not r.EntireColumn.Hidden Then
IsHidden = False
Exit Function
End If
End If
Next r
End Function

According to a quick Google search, Range.Find will not find the data if the cell is hidden if you use LookIn:=xlValues. I tested this with "Test" in Cell A6 and hid the row. This code returned Nothing:
Sub TestIt()
Dim x As Range
Set x = Range("A1:A7").Find("Test", , xlValues, xlWhole)
If x Is Nothing Then
MsgBox "Nothing"
Else
If x.EntireRow.Hidden = True Then
MsgBox x.Address & " is Hidden"
Else
MsgBox x.Address & " is Visible"
End If
End If
End Sub
Instead you need to use LookIn:=xlFormulas:
Sub TestIt()
Dim x As Range
Set x = Range("A1:A7").Find("Test", , xlFormulas, xlWhole)
If x Is Nothing Then
MsgBox "Nothing"
Else
If x.EntireRow.Hidden = True Then
MsgBox x.Address & " is Hidden"
Else
MsgBox x.Address & " is Visible"
End If
End If
End Sub
Then you can use either:
y = x.EntireRow.Hidden
or
y = x.EntireColumn.Hidden
to get your Boolean (True if the cell is hidden and False if the cell is visible)

Do you need to determine if the entire column is hidden? Individual cells can not be hidden. (Unless, of course, you're referring to the HiddenFormula property). If so, the following code should work:
y = x.entirecolumn.Hidden 'does not get the property
Let me know if this works

Related

Excel VBA .find matches when there is no match

I am attempting to find if a value in a cell matches the list of values in a named range that defines the dropdown for the cell.
My problem is if the user enters an asterik in the cell, this value is not a valid dropdown value but it validates to the first item in the list. In the code below, if szCellValue = "*" then the validation does not work.
Does anyone know how to get this search to work?
Range Values
DESK
ON-SITE
N/A
Code to determine the match
Dim bError As Boolean
Dim oCell As Range
Dim oFoundCell As Range
Dim szCellValue As String
Dim szLookupValue As String
szCellValue = CStr(Trim(oCell.Value2))
' Validate In Dropdown if Length > 0
If Len(szCellValue) > 0 Then
' See if the oCell value in the oRange loop exists in this szValidationNamedRange dropdown
Set oFoundCell = GetRangeFromNamedRange(cValidateCellData.ValidationNamedRange).Find(szCellValue, LookIn:=xlValues, Lookat:=xlWhole)
' If Value Not Found in Dropdown...or if they've typed in an id value (which will be found on odd numbered columns)
If oFoundCell Is Nothing Then
Call SetError(oCell.Text, cValidateCellData, "Not a Valid Value for drop down " + cValidateCellData.ValidationNamedRange + ".")
bError = True
End If
Else
If cValidateCellData.Required Then
Call SetError(oCell.Text, cValidateCellData, "Please input a value. This is a Required Field.")
End If
End If
You can use ~ to escape the asterisk.
Eg:
Dim bError As Boolean
Dim oCell As Range
Dim oFoundCell As Range
Dim szCellValue As String
Dim szLookupValue As String
szCellValue = CStr(Trim(oCell.Value2))
' Validate In Dropdown if Length > 0
If Len(szCellValue) > 0 Then
' See if the oCell value in the oRange loop exists in this szValidationNamedRange dropdown
' (escape * using ~)
Set oFoundCell = GetRangeFromNamedRange(cValidateCellData.ValidationNamedRange) _
.Find(Replace(szCellValue, "*", "~*"), LookIn:=xlValues, Lookat:=xlWhole)
' If Value Not Found in Dropdown...or if they've typed in an id value
' (which will be found on odd numbered columns)
If oFoundCell Is Nothing Then
Call SetError(oCell.Text, cValidateCellData, _
"Not a Valid Value for drop down " & cValidateCellData.ValidationNamedRange & ".")
bError = True
End If
Else
If cValidateCellData.Required Then
Call SetError(oCell.Text, cValidateCellData, _
"Please input a value. This is a Required Field.")
End If
End If

Find value in column and change cell to left with an if statment

This VBA script should take the value in the cell A37 and check if its in the C column of another worksheet. When the number is found the column to the left should be changed to 0. If it is already 0 then a message box will inform the user and if the number does not exist another message box will inform them of this.
This is the VBA I am using to accomplish this. However, every time I try to run it there is a "compile error: Next without For"
Update This issue now is that I need to activate the cell that the fcell is in before doing an Active.cell offset
Sub Cancelled()
Dim x As Long
Dim regRange As Range
Dim fcell As Range
x = ThisWorkbook.Sheets("Welcome").Range("A37").Value
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
For Each fcell In regRange.Cells
If fcell.Value = x Then
ActiveCell.Offset(0, -1).Select
If ActiveCell.Value = 1 Then
ActiveCell.Value = 0
MsgBox "Changed to zero"
Exit Sub
Else
MsgBox "That registration number is already cancelled"
Exit Sub
End If
End If
Next fcell
MsgBox "That number does not exist"
End Sub
Edit for new question: No need to use Select and ActiveCell
If fcell.Value = x Then
If fcell.Offset(0,-1).Value = 1 Then
fcell.Offset(0,-1).Value = 0
...
Edit 2: A further suggestion: You could also use the Range.Find method. This will throw an error if nothing is found so you have to catch that:
On Error Resume Next 'If an error occurs, continue with the next line
Set fcell = regRange.Find(x)
On Error GoTo 0 'disable the error handler
If fcell Is Nothing Then 'If Find failed
MsgBox "That number does not exist"
Else
'do your stuff with fcell here
End If
Hope this is not too late to answer your question:
Sub Cancelled()
Dim x As Long
Dim regRange As Range
Dim fcell As Range
x = ThisWorkbook.Sheets("Welcome").Range("A7").Value
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
For Each fcell In regRange.Cells
If fcell.Value = x Then
If fcell.Offset(0, -1).Value = 1 Then
fcell.Offset(0, -1).Value = 0
MsgBox "Changed to zero"
Else
MsgBox "That registration number is already cancelled"
End If
Exit Sub
End If
Next fcell
MsgBox "That number does not exist"
End Sub
Instead of
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
its better to get the last row in Column C and then set your range as:
Dim lastRow As Long
lastRow = ThisWorkbook.Sheets("Registration").Cells(Rows.Count, "C").End(xlUp).Row
Set regRange = ThisWorkbook.Sheets("Registration").Range("C1:C" & lastRow)

VBA Record date of row change in specific column

I'm trying to automatically update the "Updated" column of an excel spreadsheet when any cell of that specific row changes to today's date. I was able to do this by hard-coding where the "Updated" column header would be, however, it is now necessary to search for that column header as it may move.
The code I am trying to implement works but immediately gives me the error Automation error - The object invoked has disconnected from it's clients.
Any help would be appreciated. Here is the code I have currently:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A:DX")) Is Nothing Then
Dim f As Range
Set f = ActiveSheet.Range("A1:DD1").Find("Updated", lookat:=xlWhole)
' f.Row = Range(Target).Row
If Not f Is Nothing Then
Range(Split(f.Address, "$")(1) & Target.Row).Value = Now
Else
MsgBox "'Updated' header not found!"
End If
End If
End Sub
You got into an endless loop.
Try this:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A:DX")) Is Nothing Then
Dim f As Range
Set f = ActiveSheet.Range("A1:DD1").Find("Updated", lookat:=xlWhole)
' f.Row = Range(Target).Row
If f Is Nothing Then
MsgBox "'Updated' header not found!"
ElseIf Intersect(Target, f.EntireColumn) Is Nothing Then
Intersect(Target.EntireRow, f.EntireColumn).Value = Now
' Else
' MsgBox "We entered this function again because the row above updated the Updated column", vbInformation, "False alarm"
End If
End If
End Sub
To understand what happens,
Uncomment the else and MsgBox
Put a breakpoint on the MsgBox
When you hit it, press [ctrl]-L
In a case such as this, I run into far fewer problems when I simply loop through the available cells to find the column header. Using the .Find method also works, but is less "tunable" to my needs in a custom application.
Public Function FindColumn(header As String) As Long
Dim lastCol As Long
Dim headerCol As Long
Dim i As Long
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("VTO2 Labor")
lastCol = sh.Cells(1, sh.Columns.Count).End(xlToLeft).Column
headerCol = 0
For i = 1 To lastCol
If sh.Cells(1, i).Value = header Then
headerCol = i
End If
Next i
FindColumn = headerCol
End Function
It isn't clear on whether the Updated column header could be in row 1 or if it will always be in row 1, just not in the same location.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A:DX")) Is Nothing Then
On Error GoTo bm_SafeExit
'TURN OFF EVENTS IF YOU ARE GOING TO WRITE A VALUE INTO THE WORKSHEET!
Application.EnableEvents = False
Dim uCol As Long, f As Range
If Application.CountIf(Rows(1), "updated") Then
uCol = Application.Match("updated", Rows(1), 0)
For Each f In Intersect(Target, Range("A:DX"))
If f.Row > 1 Then _
Cells(f.Row, uCol) = Now
Next f
Else
MsgBox "'Updated' header not found!"
End If
End If
bm_SafeExit:
Application.EnableEvents = True
End Sub
That should survive multiple updates (e.g. when pasting values). The problem I see is that is the Updated column is being shifted around, presumably through inserting columns or the like, then the change routine is going to run.

Find cell based on a property

I need to find a cell into a worksheet but I'd like to avoid looking for a string.
The problem I have is that the worksheet will be edited by my client. If ever he decides to write the string I'm looking for before the good one, the app will crash.
Sub FindSpecificCell()
Dim ws As Worksheet
Set ws = ActiveWorkbook.Sheets("TP 1")
Dim myRange As Range
Dim rangeFinal As Range
Set monRange = ws.Range("A:AJ")
Set rangeFinal = myRange.Find("Description du test")
Debug.Print " "
Debug.Print "Looking for ""Description du test"" in TP 1 "
Debug.Print "column : " & rangeFinal.Column
Debug.Print "row : " & rangeFinal.Row
End Sub
Is there a way to insert a kind of property inside the cell in order to be sure that I'm working on the good one?
You can't associated properties with a specific cell directly, but you can use properties with the worksheet to store this information. I've used a couple methods like this before:
'Set the provided value of the custom property with the provided name in the provided sheet.
Private Sub SetCustomPropertyValue(InSheet As Worksheet, WithPropertyName As String, WithValue As Variant)
Dim objCP As CustomProperty
Dim bolFound As Boolean
bolFound = False 'preset.
For Each objCP In InSheet.CustomProperties
'if this property's name is the one whose value is sought...
If (StrComp(objCP.Name, WithPropertyName, vbTextCompare) = 0) Then
objCP.Value = WithValue
bolFound = True
Exit For
End If
Next
'if the property didn't already exist on the sheet, add it.
If (Not bolFound) Then Call InSheet.CustomProperties.Add(WithPropertyName, WithValue)
End Sub
'Return the value of the custom property with the provided name in the provided sheet.
Private Function GetCustomPropertyValue(InSheet As Worksheet, WithPropertyName As String) As Variant
Dim objCP As CustomProperty
GetCustomPropertyValue = Empty
For Each objCP In InSheet.CustomProperties
'if this property's name is the one whose value is sought...
If (StrComp(objCP.Name, WithPropertyName, vbTextCompare) = 0) Then
GetCustomPropertyValue = objCP.Value
Exit For
End If
Next
End Function
Then you can do something like this to write and read back values:
Sub test()
Dim strPropName As String
strPropName = "MyRange_" & Selection.Address
Dim strWhatIWantToStore As String
strWhatIWantToStore = "Here's what I want to store for this range"
Call SetCustomPropertyValue(ActiveSheet, strPropName, strWhatIWantToStore)
MsgBox GetCustomPropertyValue(ActiveSheet, strPropName)
End Sub

How can I check if a named range is in my current range?

I know how to name a range of cells.
So for example if A1 to B2 was named RangeA and I select A1 to C2, is there anyway I can tell if RangeA is inside of it?
Here is some code to help you on your way:
Sub test()
Dim r1 As Range, r2 As Range, r3 As Range
Set r1 = Range("A1:A5")
Set r2 = Range("A3:A6")
Set r3 = Application.Intersect(r1, r2)
If Not r3 Is Nothing Then
If r3.Cells.Count = r2.Cells.Count Then
MsgBox "there is a complete intersection"
Else
MsgBox "there is some overlap"
End If
Else
MsgBox "there is no overlap"
End If
End Sub
Should be pretty self-explanatory. And with a tip of the hat to #TimWilliams who posted a comment along these lines.
UPDATE based on your clarification of requirement
Sub getIntersectingNames(r As Range)
For Each Nm In ActiveWorkbook.Names
If Not Application.Intersect(r, Range(Nm)) Is Nothing Then
MsgBox Nm.Name & " intersects"
End If
Next
End Sub
Sub test()
' pop up a message box for every range that intersects cell B2:
getIntersectingNames [B2]
End Sub
Function listIntersectingNames(r As Range)
Dim result() As String
ReDim result(1 To ActiveWorkbook.Names.Count)
Dim matchCount As Integer
matchCount = 0
For Each Nm In ActiveWorkbook.Names
If Not Application.Intersect(r, Range(Nm)) Is Nothing Then
matchCount = matchCount + 1
result(matchCount) = Nm.Name
End If
Next
ReDim Preserve result(1 To matchCount)
listIntersectingNames = result
End Function
Sub test2()
' return an array of names of all ranges that intersect cell B3:
ans = listIntersectingNames([B3])
s = ""
For Each r In ans
s = s & r & vbCrLf
Next
MsgBox s
End Sub
I don't know quite how you want to "return a list of names", so I give a couple of options in the above. Let me know if you can apply this to your situation or if you need more?
Further expanding what Tim and Floris said, see below:
Dim myNamedRange As Range, mySelection As Range, myIntersect As Range
Set mySelection = Selection '~~> gets your selection
Set myNamedRange = Range("RangeA") '~~> can be declared or directly pass to Intersect
Set myIntersect = Intersect(mySelection, myNamedRange)
'~~> Then use intersect
If Not myIntersect Is Nothing Then
If myIntersect.Address = myNamedRange.Address Then
MsgBox "Your named range is within your selection."
Else
MsgBox "Your named range is beyond your selection."
End If
Else
MsgBox "Your named range is beyond your selection."
End If
End Sub
This is based on your actual question having a named range RangeA to be compared with a selection.
Hope this helps a bit.