Forum Discussion
Using a drop down to hide/unhide columns in a table (VBA Code)
- Apr 22, 2024
Michael1835 To show/hide columns based on a selected employee, first open Name Manager and define a named range for the list of employees (referring to the applicable column headers). For example, define a new name called Employee_List that refers to: =Sheet1!$I$6:$DD$6. Next, create a Data Validation List in cell G6, with Source: =Employee_List. Then, open the Visual Basic Editor and use the following code in the Worksheet_Change event of the applicable worksheet module (Sheet1, in this example):
Private Sub Worksheet_Change(ByVal Target As Range) Dim rgEmployee As Range Set rgEmployee = Me.Range("G2") If Not Intersect(Target, rgEmployee) Is Nothing Then Dim rgList As Range, str As String, n As Variant Set rgList = Range("Employee_List") str = rgEmployee.Value n = Application.Match(str, rgList, 0) If IsError(n) Then rgList.EntireColumn.Hidden = False Else rgList.EntireColumn.Hidden = True rgList.Cells(, n).EntireColumn.Hidden = False End If End If End Sub
To filter rows based on the "Check Training Status" selection in cell D2, I highly recommend looking into the Range.AutoFilter method, which will vary slightly depending on if your data is setup as a worksheet range or an Excel table (ListObject). As an example, the following code demonstrates how this could be achieved with an Excel table (ListObject), where the "Training Records out of Date" and "Retraining Required" columns are part of the table:
Sub CheckTrainingStatus() Dim tbl As ListObject, x As Long, y As Long Set tbl = Sheet1.ListObjects("Table1") x = tbl.ListColumns("Training Records out of Date").Index y = tbl.ListColumns("Retraining Required").Index With tbl.Range Select Case Sheet1.Range("D2").Value Case "Refresh" .AutoFilter Field:=x .AutoFilter Field:=y, Criteria1:="<>0" Case "Obsolete" .AutoFilter Field:=x, Criteria1:="<>0" .AutoFilter Field:=y Case Else 'All or blank .AutoFilter Field:=x .AutoFilter Field:=y End Select End With End Sub
To apply the filter to the selected employee only, modify the worksheet formulas used in columns DE and DF to reference the matching column only. For example, if the original formula in cell DE7 was:
=SUM(I7:DD7)
...you could change it to:
=IF(ISBLANK($G$2), SUM(I7:DD7), INDEX(I7:DD7, MATCH($G$2, Employee_List, 0)))
Then, modify the Worksheet_Change event to call CheckTrainingStatus as needed. For example:
Private Sub Worksheet_Change(ByVal Target As Range) Dim rgEmployee As Range Set rgEmployee = Me.Range("G2") If Not Intersect(Target, rgEmployee) Is Nothing Then Dim rgList As Range, str As String, n As Variant Set rgList = Range("Employee_List") str = rgEmployee.Value n = Application.Match(str, rgList, 0) If IsError(n) Then rgList.EntireColumn.Hidden = False Else rgList.EntireColumn.Hidden = True rgList.Cells(, n).EntireColumn.Hidden = False End If Call CheckTrainingStatus ElseIf Not Intersect(Target, Me.Range("D2")) Is Nothing Then Call CheckTrainingStatus End If End Sub
Note: this is an example only, intended to help point you in the right direction. If your data is not formatted as an Excel table (ListObject), additional steps may be required to identify the AutoFilter Range and/or the Field Numbers of the columns to be filtered.
Michael1835 To show/hide columns based on a selected employee, first open Name Manager and define a named range for the list of employees (referring to the applicable column headers). For example, define a new name called Employee_List that refers to: =Sheet1!$I$6:$DD$6. Next, create a Data Validation List in cell G6, with Source: =Employee_List. Then, open the Visual Basic Editor and use the following code in the Worksheet_Change event of the applicable worksheet module (Sheet1, in this example):
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rgEmployee As Range
Set rgEmployee = Me.Range("G2")
If Not Intersect(Target, rgEmployee) Is Nothing Then
Dim rgList As Range, str As String, n As Variant
Set rgList = Range("Employee_List")
str = rgEmployee.Value
n = Application.Match(str, rgList, 0)
If IsError(n) Then
rgList.EntireColumn.Hidden = False
Else
rgList.EntireColumn.Hidden = True
rgList.Cells(, n).EntireColumn.Hidden = False
End If
End If
End Sub
To filter rows based on the "Check Training Status" selection in cell D2, I highly recommend looking into the Range.AutoFilter method, which will vary slightly depending on if your data is setup as a worksheet range or an Excel table (ListObject). As an example, the following code demonstrates how this could be achieved with an Excel table (ListObject), where the "Training Records out of Date" and "Retraining Required" columns are part of the table:
Sub CheckTrainingStatus()
Dim tbl As ListObject, x As Long, y As Long
Set tbl = Sheet1.ListObjects("Table1")
x = tbl.ListColumns("Training Records out of Date").Index
y = tbl.ListColumns("Retraining Required").Index
With tbl.Range
Select Case Sheet1.Range("D2").Value
Case "Refresh"
.AutoFilter Field:=x
.AutoFilter Field:=y, Criteria1:="<>0"
Case "Obsolete"
.AutoFilter Field:=x, Criteria1:="<>0"
.AutoFilter Field:=y
Case Else 'All or blank
.AutoFilter Field:=x
.AutoFilter Field:=y
End Select
End With
End Sub
To apply the filter to the selected employee only, modify the worksheet formulas used in columns DE and DF to reference the matching column only. For example, if the original formula in cell DE7 was:
=SUM(I7:DD7)
...you could change it to:
=IF(ISBLANK($G$2), SUM(I7:DD7), INDEX(I7:DD7, MATCH($G$2, Employee_List, 0)))
Then, modify the Worksheet_Change event to call CheckTrainingStatus as needed. For example:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rgEmployee As Range
Set rgEmployee = Me.Range("G2")
If Not Intersect(Target, rgEmployee) Is Nothing Then
Dim rgList As Range, str As String, n As Variant
Set rgList = Range("Employee_List")
str = rgEmployee.Value
n = Application.Match(str, rgList, 0)
If IsError(n) Then
rgList.EntireColumn.Hidden = False
Else
rgList.EntireColumn.Hidden = True
rgList.Cells(, n).EntireColumn.Hidden = False
End If
Call CheckTrainingStatus
ElseIf Not Intersect(Target, Me.Range("D2")) Is Nothing Then
Call CheckTrainingStatus
End If
End Sub
Note: this is an example only, intended to help point you in the right direction. If your data is not formatted as an Excel table (ListObject), additional steps may be required to identify the AutoFilter Range and/or the Field Numbers of the columns to be filtered.
djclements Thank you for your post! The first part works perfectly for filtering the employee's. I am trying to get the second bit working (Still very new to the VBA code) and this is what I have:
Can you tell me where I am going wrong for trying to use the Range.AutoFilter method?
For reference the formula used for counting the rows with your suggestion is now:
=IF(ISBLANK($G$2), COUNTIF(BG7:DD7,">0"), INDEX(I7:DD7, MATCH($G$2, Employee_List, 0)))
This is returning me the date of the training and not a count number? The bold is what is was originally.
- djclementsApr 22, 2024Bronze Contributor
Michael1835 Sorry, I forgot to mention that I placed the CheckTrainingStatus sub routine in a separate module, which is why I referenced the sheet code (Sheet1). Since you've placed it in the worksheet module (which is fine), just change the two Sheet1 references to Me:
- Set tbl = Me.ListObjects("Template_Table")
- Select Case Me.Range("D2").Value
Regarding the worksheet formula, it's not entirely clear to me why you would only be referencing columns BG to DD, when the employee names span from columns I to DD. Perhaps there's more to the design than I'm understanding, but you could try something along these lines:
=COUNTIF(IF(ISBLANK($G$2), I7:DD7, INDEX(I7:DD7, MATCH($G$2, Employee_List, 0))), ">0")
- Michael1835Apr 22, 2024Copper Contributor
djclements A huge thank you, the VBA is working great now!
<<Regarding the worksheet formula, it's not entirely clear to me why you would only be referencing columns BG to DD, when the employee names span from columns I to DD.>>
Columns I to BF contain latest date entry for training completion and column DF compares that date with the last issue of that training to determine if the training is obsolete. Columns BG to DD are formulated to generate the date of refresher training based off the number in column E and the date entry to corresponding column.. so for example column BG is the date for refresher training from the date entered in column I.
So must ask one final question! I need to edit the formula in column DE to be able to count if the date in columns BG to DD are >0 and <today()?
- djclementsApr 22, 2024Bronze Contributor
Michael1835 Use COUNTIFS to handle multiple criteria. With Excel for MS365, you could try:
=LET(rng, IF(ISBLANK($G$2), BG7:DD7, INDEX(BG7:DD7, XMATCH($G$2, Employee_List))), COUNTIFS(rng, ">0", rng, "<"&TODAY()))
For older versions of Excel, you may have to use something like:
=IF(ISBLANK($G$2), COUNTIFS(BG7:DD7, ">0", BG7:DD7, "<"&TODAY()), --AND(INDEX(BG7:DD7, MATCH($G$2, Employee_List, 0))>0, INDEX(BG7:DD7, MATCH($G$2, Employee_List, 0))<TODAY()))
Note: these will only work when an employee from Column1 to Column50 is selected. When selecting Column51 and above, it will return the #REF! error. To make it work for all columns in Excel for MS365, try the following:
=LET(rng, IF(ISBLANK($G$2), BG7:DD7, LET(num, XMATCH($G$2, Employee_List), INDEX(BG7:DD7, IF(num>50, num-50, num)))), COUNTIFS(rng, ">0", rng, "<"&TODAY()))
And with older versions of Excel, something like this might do the trick:
=IF(ISBLANK($G$2), COUNTIFS(BG7:DD7, ">0", BG7:DD7, "<"&TODAY()), COUNTIFS($I$6:$BF$6, $G$2, BG7:DD7, ">0", BG7:DD7, "<"&TODAY()) + COUNTIFS($BG$6:$DD$6, $G$2, BG7:DD7, ">0", BG7:DD7, "<"&TODAY()))