VBA Filter Multiple Columns At Same Time - excel

Jim L from Ontario was a tremendous help in solving my first challenge. You can review that at this link : Previous Discussion
I thought it would be a simple matter to add filters for additional columns once the DATE FILTER QUESTION was solved. Nope.
I've tried adding additional filters within the same confines as the DATE filter ... I've tried adding the additional filters in the same sub but below the DATE filter ... even placing the additional filters in separate subs. Nothing is working.
The example workbook may be downloaded here : Download Workbook
The end users will have a need to filter on one or more columns at the same time. How can I work that in with the existing code in the workbook ?
I'm stumped !
Thanks.

Try adding the auto-filter across all the columns and then use each button to set the criteria for one column only. Here is an example for the fist 3 columns that you can expand to the others.
COL_FILTER is an integer parameter to the sub filterCol which is generic to all the columns you want to add a filter to (except the date which is a special case). Assign your "UNIT" filter button to the sub FilterB, "Call RCVD" button to sub FilterC etc. When you first press any button the filter drop downs appear across all columns but only 1 column will have criteria applied. Pressing further buttons will set criteria for those additional columns and retain the previous filters. Entering a blank search term will remove the criteria for that column only
Option Explicit
Sub ResetFilters()
Dim Wks As Worksheet
Set Wks = Sheets("Call Log File")
With Wks
On Error Resume Next
If Wks.AutoFilterMode Then
Wks.AutoFilterMode = False
End If
End With
End Sub
Sub FilterB()
Call filterCol(2)
End Sub
Sub FilterC()
Call filterCol(3)
End Sub
Sub filterCol(COL_FILTER As Integer)
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Call Log File")
' set auto filter to all columns if not already on
Dim rngFilter As Range
Set rngFilter = ws.Range("A2:K2")
If ws.AutoFilterMode = False Then
rngFilter.AutoFilter
End If
'Debug.Print rngFilter.Address
' get filter criteria
Dim sColname As String
sColname = ws.Cells(2, COL_FILTER)
Dim sPrompt As String, sUserInput As String, n As Integer
sPrompt = "Enter " & sColname
sUserInput = InputBox$(sPrompt)
Dim criteria(2) As String
criteria(1) = "*" & sUserInput & "*"
' apply filter to the select column
If ws.AutoFilterMode = True Then
rngFilter.AutoFilter COL_FILTER, "=" & criteria(1)
End If
End Sub
Sub FilterDate()
Const COL_FILTER As Integer = 1 ' A
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Call Log File")
' set auto filter to all columns if not already on
Dim rngFilter As Range
Set rngFilter = ws.Range("A2:K2")
If ws.AutoFilterMode = False Then
rngFilter.AutoFilter
End If
'Debug.Print rngFilter.Address
Dim sPrompt As String, sUserInput As String, n As Integer
sPrompt = "Enter DATE" & vbCrLf & _
"For YEAR ONLY: YY" & vbCrLf & _
"For YEAR & MONTH: YYMM" & vbCrLf & _
"For YEAR & MONTH & DAY: YYMMDD"
sUserInput = InputBox$(sPrompt)
n = Len(sUserInput)
If n = 0 Then
rngFilter.AutoFilter COL_FILTER ' clear filter
Exit Sub
ElseIf Not (n = 2 Or n = 4 Or n = 6) Then
MsgBox sUserInput & " is not correct", vbExclamation, "Wrong Format"
Exit Sub
End If
Dim mydate As Variant
mydate = dateRange(sUserInput)
'Debug.Print sUserInput, mydate(1), mydate(2)
If ws.AutoFilterMode = True Then
rngFilter.AutoFilter COL_FILTER, ">=" & mydate(1), 1, "<=" & mydate(2)
End If
End Sub
Function dateRange(s As String)
Dim s1 As String, s2 As String
s1 = "000"
s2 = "999"
Select Case Len(s)
Case 2
s1 = "0101" & s1
s2 = "1231" & s2
Case 4
s1 = "01" & s1
s2 = "31" & s2
Case 6
' nothing to add
Case Else
dateRange = ""
Exit Function
End Select
Dim rng(2) As Long
rng(1) = CLng(s + s1)
rng(2) = CLng(s + s2)
dateRange = rng
End Function

Fo the benefit of others ... this following macro will search for a term in Col B, after the table was filtered by Col A. Although this is not a "filtering approach" in Col B, it is very effective and does precisely what I was looking for.
Thank you to all for your assistance.
Sub FilterB()
Dim cl As Range, rng As Range
Dim sPrompt As String, sUserInput As String
Set rng = Range("B3:B100")
sPrompt = "Enter SEARCH TERM"
sUserInput = InputBox$(sPrompt)
For Each cl In rng.SpecialCells(xlCellTypeVisible)
If cl.Value <> sUserInput Then
cl.Rows.Hidden = True
End If
Next cl
End Sub
And this can be duplicated as many times as required to further "filter down" additional columns.

Related

How to use VBA to change if an Excel file is visible (minimized) based on changes to the grand totals in a pivot table?

I have a live report in Excel that shows if cranes are currently delayed. What I would like to do, by using VBA, is to make it so that when a crane either starts a delay, or ends one, the file will go from being minimized to being expanded so that a user can be signalled for when a crane has gone on delay. I would also like this to only occur for the cranes that are present within the table after being filtered.
I have attached an image of the data along with the relevant filter for cranes.
Crane Delay Data with Crane Filter
My thinking was to scan the Grand Total column, and if the value has changed from being empty to having a figure greater than 0 (and vice versa) to trigger a Macro that will make the file visible.
If possible, what code would I need to use and how would I go about doing this?
Many thanks.
For those interested, I have come up with a solution. Please leave a comment if you want me to talk through it. Code is below:
Option Explicit
Private Sub Worksheet_PivotTableAfterValueChange(ByVal TargetPivotTable As PivotTable, ByVal TargetRange As Range)
End Sub
Public Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)
Dim pt As PivotTable
Dim wsBackup As Worksheet
Dim c As Range
Dim rngPivot As Range
Dim lastCol As Long
Dim strCrane As String
Dim sValues As Variant
sValues = ArrayListOfSelectedAndVisibleSlicerItems("Slicer_QC1")
'Change to suit
Set pt = Me.PivotTables("PivotTable1")
'Where has a copy of table been saved?
Set wsBackup = ThisWorkbook.Worksheets("Pivot Copy")
Set rngPivot = pt.DataBodyRange
'How many columns?
lastCol = rngPivot.Columns.Count
Application.ScreenUpdating = False
'Check each cell in last column/grand total
For Each c In rngPivot.Columns(lastCol).Cells
'What item is this?
strCrane = c.Offset(0, -lastCol).Value
'Escape clause
If strCrane = "Grand Total" Then Exit For
If c.Value = 0 Then
'Use a function that won't throw an error
'Note we add 1 to account for row labels
If WorksheetFunction.SumIfs(wsBackup.Columns(1 + lastCol), _
wsBackup.Range("A:A"), strCrane) <> 0 Then
If IsInArray(strCrane, sValues) = True Then
ActiveWindow.WindowState = xlMaximized
MsgBox strCrane & " has ended a delay" & vbCrLf & vbCrLf & "(Minimise Excel after using file)"
End If
End If
ElseIf c.Value > 0 Then
If WorksheetFunction.SumIfs(wsBackup.Columns(1 + lastCol), _
wsBackup.Range("A:A"), strCrane) = 0 Then
If IsInArray(strCrane, sValues) = True Then
ActiveWindow.WindowState = xlMaximized
MsgBox strCrane & " has started a delay" & vbCrLf & vbCrLf & "(Minimise Excel after using file)"
End If
End If
End If
Next c
'Save our new backup
wsBackup.Cells.Clear
pt.TableRange2.Copy
wsBackup.Range("A1").PasteSpecial xlPasteValues
'Clean up
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
Function ArrayListOfSelectedAndVisibleSlicerItems(MySlicerName As String) As Variant
'This function returns an array of the limited set of items in Slicer A
'Limitation is due to both:
'(1) direct selection of items by user in slicer A
'(2) selection of items in slicer B which in consequence limits the number of items in slicer A
Dim ShortList() As Variant
Dim i As Integer: i = 0 'for iterate
Dim sC As SlicerCache
Dim sI As SlicerItem 'for iterate
Set sC = ThisWorkbook.SlicerCaches(MySlicerName)
For Each sI In sC.SlicerItems
If sI.Selected = True Then
' Debug.Print sI.Name
ReDim Preserve ShortList(i)
ShortList(i) = sI.Value
i = i + 1
End If
Next sI
ArrayListOfSelectedAndVisibleSlicerItems = ShortList
End Function
Private Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = UBound(Filter(arr, stringToBeFound)) > -1
End Function

Combine Cells into a String, so they are searchable with input box

I am attempting to create an Excel sheet based on an already existing workbook. I can't change the format of the workbook, so I am stuck with what follows.
We are creating a system to use a hand scanner with barcodes containing staff names, for the purposes of tracking COVID testing.
Our workbook has one column for first name, one for last.
So B2 = Cluff, C2 = Aaron
How do I use an input box (for the scanner) that searches the string "Cluff, Aaron" (user input in the box, not the name specifically in the code) and returns the row with the data above?
Bonus points if it opens a new input box to enter the test UPC (another input looking for a string), and inputs into Column AA on the same row.
My knowledge of VBA is very limited.
I tried various edits to the following code:
Sub DualFind()
Dim vFind1 As String, vFind2 As String
Dim rFound As Range, lLoop As Long
Dim bFound As Boolean
Dim rLookIn1 As Range, rLookIn2 As Range
vFind1 = InputBox("Find What: First value?", "FIND FIRST VALUE")
If vFind1 = vbNullString Then Exit Sub
vFind2 = InputBox("Find What: Second value?", "FIND SECOND VALUE")
If vFind2 = vbNullString Then Exit Sub
If Selection.Areas.Count > 1 Then
Set rLookIn1 = Selection.Areas(1).Columns(1)
Set rLookIn2 = Selection.Areas(2).Columns(1)
Else
Set rLookIn1 = Selection.Columns(1)
Set rLookIn2 = Selection.Columns(2)
End If
Set rFound = rLookIn1.Cells(1, 1)
For lLoop = 1 To WorksheetFunction.CountIf(rLookIn1, vFind1)
Set rFound = rLookIn1.Find(What:=vFind1, After:=rFound, LookAt:=xlWhole)
If UCase(rLookIn2.Cells(rFound.Row, 1)) = UCase(vFind2) Then
bFound = True
Exit For
End If
Next lLoop
If bFound = True Then
MsgBox "Match found", vbInformation, "ozgrid.com"
Range(rFound, rLookIn2.Cells(rFound.Row, 1)).Select
Else
MsgBox "Sorry, no match found", vbInformation, "ozgrid.com"
End If
End Sub
From what I gather, it needs two separate inputs to search the columns. I need it to search two columns with one input. I imagine you'd have to compile columns B and C into a string, and then search it based on input from the box.
Not entirely sure if this is what you were after but hopefully it will at least give you some more ideas. I've written this so that the user inputs the search name in the format [first name] [last name], e.g. Aaron Cluff. I've assumed from what you've written that the last name is found in column 2 and the first name in column 3.
Sub Demo()
Dim SearchName As String
Dim UPC As String
Dim LastRow As Long
Dim Row As Long
Dim RowMatch As Long
Dim ColFirstName As Integer
Dim ColLastName As Integer
ColLastName = 2
ColFirstName = 3
SearchName = InputBox("Enter search name: e.g. Aaron Cuff", "Search")
If SearchName = "" Then Exit Sub
SearchName = Trim(SearchName)
LastRow = Cells(Rows.Count, ColLastName).End(xlUp).Row
For Row = 1 To LastRow
If StrComp(SearchName, Trim(Cells(Row, ColFirstName)) & " " & Trim(Cells(Row, ColLastName)), vbTextCompare) = 0 Then
RowMatch = Row
Exit For
End If
Next
If RowMatch = 0 Then
MsgBox "Search Name: " & StrConv(SearchName, vbProperCase) & vbNewLine & vbNewLine & _
"No Match Found", vbInformation, "Search Result"
Exit Sub
End If
UPC = InputBox("Enter Test UPC for " & StrConv(SearchName, vbProperCase) & ": ", "Input")
If UPC <> "" Then
Cells(RowMatch, "AA") = UPC
End If
End Sub
Here's one way to do the staff name lookup:
Dim ws As Worksheet
Dim rng As Range, m, staffName
Set ws = Worksheets("Staff")
staffName = "Cluff, Aaron"
m = ws.Evaluate("MATCH(""" & staffName & """,A1:A1000 & "", "" &B1:B1000,0)")
If Not IsError(m) Then
Debug.Print "Matched on row " & m
Else
Debug.Print "No match for " & staffName
End If

how to iterate over all rows of a excel sheet in VBA

I have this code (This code is in Access VBA which tries to read an excel file and after checking, possibly import it):
Set ExcelApp = CreateObject("Excel.application")
Set Workbook = ExcelApp.Workbooks.Open(FileName)
Set Worksheet = Workbook.Worksheets(1)
now I want to iterate over all rows of the excel worksheet. I want something such as this:
for each row in Worksheet.rows
ProcessARow(row)
next row
where
function ProcessARow(row as ???? )
' process a row
' how Should I define the function
' how can I access each cell in the row
' Is there any way that I can understand how many cell with data exist in the row
end function
My questions:
How to define the for each code that it iterate correctly on all
rows that has data?
How to define ProcessARow properly
How to get the value of each cell in the row.
How to find how many cell with data exist in the row?
Is there any way that I detect what is the data type of each cell?
edit 1
The link solves on problem :
How to define the for each code that it iterate correctly on all rows that has data?
but what about other questions?
For example, how to define ProcessARow correctly?
If you need the values in the Row, you need use the 'Value' Property and after do an cycle to get each value
for each row in Worksheet.rows
Values=row.Value
For each cell in Values
ValueCell=cell
next cell
next row
Unfortunately you questions are very broad however I believe the below sub routine can show you a few ways of achieving what you are after. In regards to what datatype each cell is more involved as it depends what data type you wish to compare it to however I have included some stuff to hopefully help.
sub hopefullyuseful()
dim ws as worksheet
dim rng as Range
dim strlc as string
dim rc as long, i as long
dim lc as long, j as long
dim celltoprocess as range
set ws = activeworkbook.sheets(activesheet.name)
strlc = ws.cells.specialcells(xlcelltypeLastCell).address
set rng = ws.range("A1:" & lc)
rc = rng.rows.count()
debug.print "Number of rows: " & rc
lc = rng.columns.count()
debug.print "Number of columns: " & lc
'
'method 1 looping through the cells'
for i = 1 to rc
for j = 1 to lc
set celltoprocess = ws.cells(i,j)
'this gives you a cell object at the coordinates of (i,j)'
'[PROCESS HERE]'
debug.print celltoprocess.address & " is celltype: " & CellType(celltoprocess)
'here you can do any processing you would like on the individual cell if needed however this is not the best method'
set celltoprocess = nothing
next j
next i
'method 2 looping through the cells using a for each loop'
for each celltoprocess in rng.cells
debug.print celltoprocess.address & " is " & CellType(celltoprocess)
next celltoprocess
'if you just need the data in the cells and not the actual cell objects'
arrOfCellData = rng.value
'to access the data'
for i = lbound(arrOfCellData,1) to ubound(arrOfCellData,1)
'i = row'
for j = lbound(arrOfCellData,2) to ubound(arrOfCellData,2)
'j = columns'
debug.print "TYPE: " & typename(arrOfCellData(i,j)) & " character count:" & len(arrOfCellData(i,j))
next j
next i
set rng=nothing
set celltoprocess = nothing
set ws = nothing
end sub
Function CellType(byref Rng as range) as string
Select Case True
Case IsEmpty(Rng)
CellType = "Blank"
Case WorksheetFunction.IsText(Rng)
CellType = "Text"
Case WorksheetFunction.IsLogical(Rng)
CellType = "Logical"
Case WorksheetFunction.IsErr(Rng)
CellType = "Error"
Case IsDate(Rng)
CellType = "Date"
Case InStr(1, Rng.Text, ":") <> 0
CellType = "Time"
Case IsNumeric(Rng)
CellType = "Value"
End Select
end function
sub processRow(byref rngRow as range)
dim c as range
'it is unclear what you want to do with the row however... if you want
'to do something to cells in the row this is how you access them
'individually
for each c in rngRow.cells
debug.print "Cell " & c.address & " is in Column " & c.column & " and Row " & c.row & " has the value of " & c.value
next c
set c = nothing
set rngRow = nothing
exit sub
if you want your other questions answered you will have to be more specific as to what you are trying to accomplish
While I like the solution offered by #krazynhazy I believe that the following solution might be slightly shorter and closer to what you asked for. Still, I'd use the CellType function offered by Krazynhazy rather than all the Iif I currently have in the below code.
Option Explicit
Sub AllNonEmptyCells()
Dim rngRow As Range
Dim rngCell As Range
Dim wksItem As Worksheet
Set wksItem = ThisWorkbook.Worksheets(1)
On Error GoTo EmptySheet
For Each rngRow In wksItem.Cells.SpecialCells(xlCellTypeConstants).EntireRow.Rows
Call ProcessARow(wksItem, rngRow.Row)
Next rngRow
Exit Sub
EmptySheet:
MsgBox "Sheet is empty." & Chr(10) & "Aborting!"
Exit Sub
End Sub
Sub ProcessARow(wksItem As Worksheet, lngRow As Long)
Dim rngCell As Range
Debug.Print "Cells to process in row " & lngRow & ": " & wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants).Count
For Each rngCell In wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants)
Debug.Print "Row: " & lngRow, _
"Column: " & rngCell.Column, _
"Value: " & rngCell.Value2, _
IIf(Left(rngCell.Formula, 1) = "=", "Formula", IIf(IsDate(rngCell.Value), "Date", IIf(IsNumeric(rngCell.Value2), "Number", "Text")))
Next rngCell
End Sub
Note, that you have to call the sub to call a row must also include the sheet on which a row should be processed.

Excel VBA : Find out which row has filter enabled

I am looking at various options in VBA to find out the row number in a worksheet that has filter enabled.
If ThisWorkbook.Sheets(1).AutoFilterMode = True Then
The above line checks only if the sheet contains filters, but I need to know which row number has the filters on.
This should do your job.
Sub test()
Dim rngRange As range
If ThisWorkbook.Sheets(1).AutoFilterMode = True Then
Set rngRange = ThisWorkbook.Sheets(1).AutoFilter.range
MsgBox "Address of Filter: " & rngRange.Address & Chr(10) _
& "Row Number is: " & rngRange.Row, vbOKOnly
End If
Set rngRange = Nothing
End Sub
This is a straight answer to your question:
Function CheckWhichRowHasFilter(r As range)
For Each rowi In r.Rows' Dim rowi As range
Set pa = rowi.Parent.AutoFilter'Dim pa As AutoFilter
If pa.FilterMode = True Then
CheckWhichRowHasFilter = pa.range.Address
Exit For
End If
Next rowi
End Function
This is how to iterate through Filters. The Property you want to check is Filter.On
Sub IterateThroughFilters()
Dim r As range
Set r = Selection
Dim rc As range
For Each rc In r.Columns
If Not rc.Parent.AutoFilter Is Nothing Then
Set currentColumnFilter = rc.Parent.AutoFilter ' Filteraddress: = currentColumnFilter.range.Address
Dim ccf As filters
Set ccf = currentColumnFilter.filters
Dim cf1 As filter
Set cf1 = ccf(1) 'onebased index
If cf1.On Then 'Here you check if filter is on
cfc1 = cf1.Criteria1(1)
cfc2 = cf1.Criteria1(2)
cfc3 = cf1.Criteria1(3)
End If
End If
Next rc
End Sub

Defining a range from values in another range

I have an excel file of tasks which have either been completed or not, indicated by a Yes or No in a column. Ultimately I am interested in data in a different column but I want to set up the code so it ignores those rows where the task has been completed. So far I have defined the column range containing the yes/no's but I don't know which command to run on this range. I imagine I want to define a new range based on the value in column C.
Option Explicit
Sub Notify()
Dim Chk As Range
Dim ChkLRow As Long
Dim WS1 As Worksheet
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
'--> If the text in column C is Yes then Ignore (CountIF ?)
'--> Find last cell in the column, set column C range as "Chk"
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
Set Chk = .Range("C1:C" & ChkLRow)
End With
'--> Else Check date in column H
'--> Count days from that date until today
'--> Display list in Message Box
Reenter:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
Application.ScreenUpdating = True
End Sub
Would it perhaps be easier to simply define one range based on the values in column C rather than first defining column C as the range and then redefining it?
Thanks
Yes Column H has the date the task 'arrived' and I want to display a count from then to the current date. The tasks are identified by a 4 digit code in Column A. I envisage the message box saying Task '1234' outstanding for xx days. – Alistair Weir 1 min ago
Is this what you are trying? Added Col I for visualization purpose. It holds no significance otherwise.
Option Explicit
Sub Notify()
Dim WS1 As Worksheet
Dim Chk As Range, FltrdRange As Range, aCell As Range
Dim ChkLRow As Long
Dim msg As String
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
'~~> Set your relevant range here
Set Chk = .Range("A1:H" & ChkLRow)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
With Chk
'~~> Filter,
.AutoFilter Field:=3, Criteria1:="NO"
'~~> Offset(to exclude headers)
Set FltrdRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
For Each aCell In FltrdRange
If aCell.Column = 8 And _
Len(Trim(.Range("A" & aCell.Row).Value)) <> 0 And _
Len(Trim(aCell.Value)) <> 0 Then
msg = msg & vbNewLine & _
"Task " & .Range("A" & aCell.Row).Value & _
" outstanding for " & _
DateDiff("d", aCell.Value, Date) & "days."
End If
Next
End With
End With
'~~> Show message
MsgBox msg
Reenter:
Application.ScreenUpdating = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
End Sub
SNAPSHOT
Why not brute force it.
Dim r_table as Range, i as Integer, N as Integer
' Start from the top
Set r_table = Sheets("2011").Range("C1")
' Find the last entry on column C and count the # of cells
N = Sheets("2011").Range(r_table, r_table.End(xlDown)).Rows.Count
Dim table_values() as Variant
' This will transfer all the values from the spreadsheet into an VBA array
' and it works super fast. Access values with A(row,col) notation.
table_values = r_table.Resize(N, 5).Value2 ' No. of columns is 5 ?
For i=1 to N
If table_values(i,1)="Yes" Then 'Check Column C
Else
... table_values(i,5) ' Column H
End if
Next i
MsgBox ....
This will be super fast, with no flicker on the screen.

Resources