Looping through multiple named ranges and going to similar named range VBA - vba

I would like to check multiple named ranges for a boolean True/False value. I need to check the 1-cell validation ranges (in order) and if the result is True, I need to .Select the corresponding named range (i.e., the named range without the corresponding "validation_" prefix and exit the subroutine. The following code works, but it's not DRY.
Here's a snippet to get the gist of the question, but this If-ElseIf continues for many other named ranges:
If Range("validation_name") = True Then
Range("name").Select
Exit Sub
ElseIf Range("validation_category") = True Then
Range("category").Select
Exit Sub
ElseIf Range("validation_subcategory") = True Then
Range("subcategory").Select
Exit Sub
' ... and many more...
Possibilities/Questions:
I think I could utilize either an array of named ranges and an array of resulting "go-to" named ranges?
Perhaps I could use a collection instead?
Not sure if a for loop or a while loop would be better?

some "extra dry" codes
Sub main()
Dim a As Variant
For Each a In Array("name", "category", "subcategory")
If Range("valid_" & a).Value = True Then
Range(a).Select
Exit Sub
End If
Next a
End Sub
Sub main2()
Dim r As Range, f As Range
Set r = Union(Range("validation_date"), Range("validation_name"), Range("validation_subcategory"), Range("validation_category"))
Set f = r.Find(what:="true", LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False)
If Not f Is Nothing Then Range(Replace(f.name.name, "validation_", "")).Select
End Sub

I assume that these are 1-cell ranges. If so, the following should work:
Sub SelectRange()
Dim i As long, A As Variant
A = Array("validation_name", "validation_category", "validation_subcategory")
For i = 0 To UBound(A)
If Range(A(i)).Value = True Then
Range(Split(A(i),"_")(1)).Select
Exit Sub
End If
Next i
End Sub

You can loop through all of the named ranges with something like:
Dim xlName As Name
For each xlName In ActiveWorkbook.Names
If xlName.Name Like "validation_*" And Range(xlName.Name) = True Then
Application.Goto Replace(xlName.Name, "validation_", ""), True
Exit Sub
End If
Next
or specify them like this
For each strName In Split("name category subcategory")
If Range("validation_" & strName) = True Then
Application.Goto strName, True
Exit Sub
End If
Next
Update
Sorry, I did not read the whole question. Seems like you can use some kind of key - value Collection
pairs = Array( Array( "validation_name", "name" ), _
Array( "validation_category", "category" ), _
Array( "validation_subcategory", "subcategory" ) )
For each pair In pairs
If Range( pair(0) ) = True Then
Application.Goto pair(1), True
Exit Sub
End If
Next

Related

If condition associated with applying a column filter

I'm trying to use an if-condition regarding filters. I wish I could write a code to check if a specific filter is applied and then do something... (of course)
My first attempt was:
If ActiveSheet.Range("$D$4:$Q$20").AutoFilter Field:=2 then
Rows("22:22").Select
Selection.EntireRow.Hidden = True
End If
In the very first line, VBA doesn't accept the condition that is written...
Any guess?
tks
This will tell you if a range is in a filter and the filter is active:
Public Function IsFilterOn(rng As Range) As Boolean
Dim ws As Worksheet
Dim iFilterNum As Integer
Set ws = rng.Parent
If Not ws.AutoFilter Is Nothing Then
If Not Application.Intersect(rng, ws.AutoFilter.Range) Is Nothing Then
With ws.AutoFilter.Filters
iFilterNum = rng.Column - .Item(1).Parent.Range.Column + 1
If iFilterNum <= .Count Then
IsFilterOn = .Item(iFilterNum).On
Exit Function
End If
End With
End If
End If
End Function
Note that you don't need to select a row before hiding it.
Sub HideWhenFiltered()
With ActiveSheet
If .AutoFilterMode Then
If .AutoFilter.Filters(2).On Then
.Rows(22).Hidden = True
End If
End If
End With
End Sub

VBA Macro triggering too often

My worksheet is set up with data validation dropdowns and I am wanting a macro to ONLY trigger when the value of the cell is changed from another value in the dropdown, not from the default "empty" value.
Here is what I am trying to use:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 5 Then
If IsEmpty(Target.Value) = True Then
MsgBox "Test1"
Else
MsgBox "Test2"
End If
End If
exitHandler:
Application.EnableEvents = True
Exit Sub
End Sub
My problem is that this "IsEmpty" command is reading the cell AFTER the selection not before. I want it to read what the cells value was BEFORE the selection not after.
How can I do this?
Example approach:
Const COL_CHECK As Long = 5
Private oldVal
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
Set c = Target.Cells(1) '<< in case multiple cells are changed...
If c.Column = COL_CHECK Then
If oldVal <> "" Then
Debug.Print "changed from non-blank"
Else
Debug.Print "changed from blank"
End If
End If
exitHandler:
Application.EnableEvents = True
Exit Sub
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim c As Range
Set c = Target.Cells(1)
oldVal = IIf(c.Column = COL_CHECK, c.Value, "")
Debug.Print "oldVal=" & oldVal
End Sub
Another approach:
This will need one cell per validation-dropdown:
Function ValChange(Cell2Follow As Range) As String
ValChange = ""
If Len(Application.Caller.Text) = 0 Then Exit Function
If Application.Caller.Text = Cell2Follow.Text Then Exit Function
MsgBox "value of the cell is changed from another value in the dropdown" & vbLf & "not from the default 'empty' value"
End Function
in a different cell, assumed the dropdown is in E6:
=E6&ValChange(E6)
application.caller.text will be the old value
(calculation must be automatic)

Why is VBA array not loading integers

Here is a function I found online to help loop through values and select pivot filter items that match the values. My problem is that the array created in the Sub Filter_Bana does not get loaded with the values from the range named as "Bana" (aka varItemList). The range "Bana" consists of about twenty numbers (integers). When I run the sub (at the bottom), I keep receiving the MsgBox "None of filter list items found" from the function. I have been trying to figure this out for a while and don't think any of the named integer list "Bana" is getting loaded into "varItemList." In other words, when varItemList is passed to the function, the array is empty. Please see code:
**EDIT: I found the problem. The problems I was having related to two issues: 1) I am bad at VBA, 2) the data type of the pivot items in my pivot field did not match the data type of the array. I switched the array components to a 5-character text and adjusted the SQL query to bring in my investorNumber as a 5-character text (i.e. the array needed to be loaded with a character string and my pivot field needed data of the character type; if there was a way to do this with integers, I'd love to know. **
Private Function Filter_PivotField(pvtField As PivotField, _
varItemList As Variant)
Dim strItem1 As Long, blTmp As Boolean, i As Long
On Error Resume Next
Debug.Print varItemList
Application.ScreenUpdating = False
With pvtField
If .Orientation = xlPageField Then .EnableMultiplePageItems = True
For i = LBound(varItemList) To UBound(varItemList)
blTmp = Not (IsError(.PivotItems(varItemList(i)).Visible))
If blTmp Then
strItem1 = .PivotItems(varItemList(i))
Exit For
End If
Next i
If strItem1 = "" Then
MsgBox "None of filter list items found."
Exit Function
End If
.PivotItems(strItem1).Visible = True
For i = 1 To .PivotItems.Count
If .PivotItems(i) <> strItem1 And _
.PivotItems(i).Visible = True Then
.PivotItems(i).Visible = False
End If
Next i
For i = LBound(varItemList) To UBound(varItemList)
.PivotItems(varItemList(i)).Visible = True
Next i
End With
Application.ScreenUpdating = True
End Function
Sub Filter_Bana()
Filter_PivotField _
pvtField:=Sheets("Pres1&2_Pivot").PivotTables("PivotTable1").PivotFields("investorNumber"), _
varItemList:=Application.Transpose(Sheets("Controls").Range("Bana"))
End Sub`
This code works. The only other key is making sure my array is loaded with character type data (not integers) and that the pivot field also contains character type data (adjusted in the query/not displayed)
Private Function Filter_PivotField(pvtField As PivotField, _
varItemList As Variant)
Dim strItem1 As String, blTmp As Boolean, i As Long
Application.ScreenUpdating = False
With pvtField
If .Orientation = xlPageField Then .EnableMultiplePageItems = True
For i = LBound(varItemList) To UBound(varItemList)
blTmp = Not (IsError(.PivotItems(varItemList(i)).Visible))
If blTmp Then
strItem1 = .PivotItems(varItemList(i))
Exit For
End If
Next i
If strItem1 = "" Then
MsgBox "None of filter list items found."
Exit Function
End If
.PivotItems(strItem1).Visible = True
For i = 1 To .PivotItems.Count
If .PivotItems(i) <> strItem1 And _
.PivotItems(i).Visible = True Then
.PivotItems(i).Visible = False
End If
Next i
For i = LBound(varItemList) To UBound(varItemList)
.PivotItems(varItemList(i)).Visible = True
Next i
End With
Application.ScreenUpdating = True
End Function
Sub Filter_Bana()
Filter_PivotField _
pvtField:=ThisWorkbook.Worksheets("Pres1_2_Pivot").PivotTables("PivotTable1").PivotFields("investorNumber"), _
varItemList:=Application.Transpose(Sheets("Controls").Range("Bana"))
End Sub

Set starting point for UsedRange

I have a combobox drop down that populates items from a list, with a function to filter to dropdown options by characters type in the combobox gathered by the following code
Option Explicit
Private cLstPrior As Variant
Private Sub Worksheet_SelectionChangePrior(ByVal Target As Range)
cLstPrior = Application.Transpose(Database.Columns("1:1").SpecialCells(xlCellTypeConstants, 23)) 'set module-level variable
Tool.priorCmb.List = cLstPrior 'initialize ComboBox to range Col A (UsedRange only)
Tool.priorCmb.ListIndex = -1 'set ComboBox value to empty
End Sub
Private Sub priorCmb_Change()
filterComboListPrior Tool.priorCmb, cLstPrior
End Sub
Private Sub priorCmb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Tool.priorCmb.DropDown
End Sub
Private Sub priorCmb_GotFocus() 'or _MouseDown()
Tool.priorCmb.DropDown
End Sub
Public Sub filterComboListPrior(ByRef cmbPrior As ComboBox, ByRef dLstPrior As Variant)
Dim itmPrior As Variant, lstPrior As String, selPrior As String
Application.EnableEvents = False
With cmbPrior
selPrior = .Value
If IsEmpty(cLstPrior) Then cLstPrior = Worksheets("Database").Columns("1:1").SpecialCells(xlCellTypeConstants, 23)
For Each itmPrior In cLstPrior
If Len(itmPrior) > 1 Then If InStr(1, itmPrior, selPrior, 1) Then lstPrior = lstPrior & itmPrior & "||"
Next
If Len(lstPrior) > 1 Then .List = Split(Left(lstPrior, Len(lstPrior) - 2), "||") Else .List = dLstPrior
End With
Application.EnableEvents = True
End Sub
The data the combobox needs to populate with is all from Column 1 in this case, any cell with characters in it.
The issue is that there are blank cells at A1 and A2, so blank entries populate the combobox dropdown later on. I am trying to force the range to only include cells with values in them, but am getting an application-defined or object-defined error at If IsEmpty(cLstPrior) Then cLstPrior = Worksheets("Database").Columns("1:1").SpecialCells(xlCellTypeConstants, 23)
I can't seem to figure this out. Also, is my Application.Transpose behavior correct or not needed?
Instead of:
Database.UsedRange.Rows(2)
Try:
Database.Range(Database.Cells(2,2),Database.Cells(Database.UsedRange.Rows.Count, 2))
May be best to use specialcells, and loop through the cells that have values.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rRng As Range, c As Range, ws As Worksheet
Set ws = Sheets("Database")
Me.ComboBox1.Clear
Set rRng = ws.Rows("2:2").SpecialCells(xlCellTypeConstants, 23)
For Each c In rRng.Cells
Me.ComboBox1.AddItem c
Next c
End Sub
Use Intersect to exclude columns
With Worksheets("Database")
Set rng = Application.Intersect(.UsedRange.Rows(2), .Cells.Resize(.Columns.Count - 1).Offset(1))
End With
Change the line in question to the newly defined range
If IsEmpty(cLst) Then cLst = rng

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.