Best way to write repeated code in VBA Dependent List of Values - excel

I have total 15 columns with Data Validation Drop-down.
Drop-down for Column 2 to 15 are dependent on value selected in Column 1.
Drop-down for Column 3 is dependent on value selected in Column 2. If Column 3 value is selected directly then value for Column 2 must be populated in the excel cell of column 2.
Drop-down for Column 5 and 6 are dependent on Column 4 and so on.
Please find the code below for Column 2 and 3 dependent on Column 1 which is very lengthy. Is there any best/dynamic way to write this code to make sure all of my requirement is satisfied in VBA?
Dim rowNum As Long
Dim ListBoxRow As Long
Dim filterRowNum As Long
Dim col2 As String
Dim col3 As String
rowNum = 2
filterRowNum = 2
col2 = ""
col3 = ""
Do Until Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 1).Value = ""
If InStr(1, Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 1).Value, activeCellValue, vbTextCompare) Then
If filterRowNum = 2 Then
col2 = col2 & Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 2).Value
col3 = col3 & Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 5).Value
Else
If InStr(col2, Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 2).Value) = 0 Then
col2 = col2 & "," & Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 2).Value
End If
If InStr(col3, Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 5).Value) = 0 Then
col3 = col3 & "," & Worksheets("Col1Col2Col3Sheet").Cells(rowNum, 5).Value
End If
End If
filterRowNum = filterRowNum + 1
End If
rowNum = rowNum + 1
Loop
With Worksheets("CustomSpreadsheet").Range("F" & ActiveCellRow).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=col2
.IgnoreBlank = True
.InCellDropdown = True
.ShowError = True
End With
With Worksheets("CustomSpreadsheet").Range("G" & ActiveCellRow).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=col3
.IgnoreBlank = True
.InCellDropdown = True
.ShowError = True
End With

Here's one way - abstract out all of the work to separate methods: one to give you a list of all unique matches, and the other to set up the validation:
'Set a cell validation list
'You might consider expanding this to also delete any previously-selected
' value if it's not in the list of values being added
Sub SetCellList(c As Range, lst As String)
With c.Validation
.Delete
If Len(lst) > 0 Then 'make sure there's something to add...
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=lst
.IgnoreBlank = True
.InCellDropdown = True
.ShowError = True
End If
End With
End Sub
'Return a comma-delimited list of all unique "sub-values"
'columnsToCheck: array of cell at the top(s) of the column(s) you want to scan for matches
'valuestoCheck: array of the value to look for in columnsToCheck
'returnColLetter: when a match is made, return all unique values from this column
Function Uniques(columnsToCheck, valuestoCheck, returnColLetter As String) As String
Dim arrA, arrB, rngCheck As Range, r As Long, i As Long, c As Range, n As Long, rng As Range
Dim dict As Object, bMatch As Boolean
Set dict = CreateObject("scripting.dictionary")
'translate array of column header cells to array of 2D arrays
'pick up values from return column
For i = LBound(columnsToCheck) To UBound(columnsToCheck)
Set c = columnsToCheck(i)
If i = LBound(columnsToCheck) Then
Set rng = c.Parent.Range(c, c.Parent.Cells(Rows.Count, c.Column).End(xlUp))
arrB = rng.EntireRow.Columns(returnColLetter).Value 'values to return
n = rng.Rows.Count '# of rows
Else
Set rng = c.Resize(n, 1) 'columns should be all the same size...
End If
columnsToCheck(i) = ToArray(rng)
Next i
'loop rowa to check and collect any matches
For r = 1 To n
bMatch = True
'loop columns to check
For i = LBound(columnsToCheck) To UBound(columnsToCheck)
If columnsToCheck(i)(r, 1) <> valuestoCheck(i) Then
bMatch = False
Exit For
End If
Next i
If bMatch Then dict(arrB(r, 1)) = True
Next r
Uniques = Join(dict.keys, ",")
End Function
'Safely get a 2D array from a range, even if the range is a single cell
Function ToArray(rng As Range)
Dim arr(1 To 1, 1 To 1)
If rng.Cells.Count = 1 Then
arr(1, 1) = rng.Value
ToArray = arr
Else
ToArray = rng.Value
End If
End Function
Example usage using a change event:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, c As Range, v
'this range depends only on the cell to the left
Set rng = Application.Intersect(Target, Me.Range("N2:N25"))
If Not rng Is Nothing Then
For Each c In rng.Cells
v = Trim(c.Value)
If Len(v) > 0 Then
'set list in next column over
SetCellList c.Offset(0, 1), _
Uniques(Array(lists.Cells(1, "A")), _
Array(v), "B")
End If
Next c
End If
'this range depends only on the two cells to the left
Set rng = Application.Intersect(Target, Me.Range("O2:O25"))
If Not rng Is Nothing Then
For Each c In rng.Cells
v = Trim(c.Value)
If Len(v) > 0 Then
'set list in next column over
SetCellList c.Offset(0, 1), _
Uniques(Array(lists.Cells(1, "A"), lists.Cells(1, "B")), _
Array(Trim(c.Offset(0, -1).Value), Trim(c.Value)), "C")
End If
Next c
End If
End Sub

Related

Worksheet change Event working like after update event of combobox

Ok i want to make a searchable drop down list data validation without using helper column or combobox control...So how can i accomplish this..Everything is working fine but let say if i put at then click drop down arrow it will not calculate the worksheet change event..I want to run the worksheet change event every time value change in a certain cell..Suppose if i type at then worksheet change event should run 2 times..I mean every time i click in a keyboard alphabet then the worksheet change event need to run.How can i accomplish this...
Here is my code:
Worksheet Change Event:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim DestinationRng As Range, SourceRng As Range
Set SourceRng = Range("A1:A25")
Set DestinationRng = Range("C1:C25")
If Not Application.Intersect(DestinationRng, Range(Target.Address)) Is Nothing Then
'Target.Validation.Delete
If Target.Value = "" Then
DVDL SourceRng, Target, ""
Else
DVDL SourceRng, Target, Range(Target.Address).Value
End If
End If
End Sub
Here is worksheet selection change event
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim DestinationRng As Range, SourceRng As Range
Set SourceRng = Range("A1:A25")
Set DestinationRng = Range("C1:C25")
' 'If Target = ActiveCell Then Debug.Print yes
Debug.Print "Active:" & ActiveCell.Address
Debug.Print Target.Address
If Not Application.Intersect(DestinationRng, Range(Target.Address)) Is Nothing Then
If Target.Value = "" Then
DVDL SourceRng, Target, ""
Else
DVDL SourceRng, Target, Range(Target.Address).Value
End If
End If
End Sub
Here is the data validation sub procedure:
Public Sub DVDL(SourceRng As Range, PlaceRng As Range, SearchTxt As String)
Dim arr As Variant, arr2 As Variant
If SourceRng.Columns.Count > 1 Then
arr = Application.WorksheetFunction.Transpose(Application.WorksheetFunction.Transpose(SourceRng.Value))
ElseIf SourceRng.Rows.Count > 1 Then
arr = Application.WorksheetFunction.Transpose(SourceRng.Value)
End If
arr = RemoveDuplicateS(arr)
If SearchTxt = "" Then
arr2 = arr
Else
arr2 = Filter(arr, SearchTxt, , vbTextCompare)
End If
For Each el In arr2
Debug.Print el
Next el
If LBound(arr2) <> UBound(arr) Then
'PlaceRng.Select
With PlaceRng.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=Join(arr2, ",")
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = False
End With
End If
End Sub
Here is the code for Removing duplicate value from source range:Not required if source contain unique value
Public Function RemoveDuplicateS(arr As Variant) As Variant
'From this function we return an array from an sorted array.
'It doesn't required extra space in memory because we use same array.
Dim i As Long, j As Long
If IsEmpty(arr) Then
RemoveDuplicateS = arr(0) 'If incoming array is empty then return empty one.
Else
j = LBound(arr)
For i = LBound(arr) To UBound(arr) - 1 'Run loop from first one to second last one.
If arr(i) <> arr(i + 1) Then 'if arr(5)<>arr(6) then put put the arr(5) value to the unique list.
arr(j) = arr(i)
j = j + 1 'Increase the j for indexing.
End If
Next i
arr(j) = arr(UBound(arr)) 'Put the last data to unique list.
ReDim Preserve arr(LBound(arr) To j) 'Delete the extra data from the array.
RemoveDuplicateS = arr 'Return the array.
End If
End Function

Validation List of Cell with filtered values of Table Header

I want to put to Data validation for a column based on Headers of a named table.
Users will add more columns with country name as headers.
I have tried giving data validation the cell to named range, Named range value is =TripCost[#Headers] 'TripCost is the the name of the table.
But I am getting all the values. I want to ignore whichever value start with "Remark" or "Cost".
Is there a way to achieve this?
Try this code, please. It will create a validation for the active cell, using as many countries your named range will contain:
Private Sub selectiveNameValidation()
Dim sh As Worksheet, rng As Range, arrH As Variant, El As Variant, strList As String
Set sh = ActiveSheet
Set rng = ActiveCell 'use here what range you need
'arrH = Range("Headers").Value 'use here a named range for the headers in discussion ("Headers")
'or use your Table headers:
arrH = sh.ListObjects("TripCost").HeaderRowRange.Value' load the range in an array
For Each El In arrH
If Not (InStr(El, "Cost") > 0 Or InStr(El, "Remark") > 0) Then
strList = strList & IIf(strList = "", "", ",") & El 'build the list string
End If
Next
With rng.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=strList
.IgnoreBlank = True
.InCellDropdown = True
.ShowInput = True
.ShowError = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
End With
End Sub
If other strings to be excluded will appear, you must only extend the line
If Not (InStr(El, "Cost") > 0 Or InStr(El, "Remark") > 0) Then
with the new one:
If Not (InStr(El, "Cost") > 0 Or InStr(El, "Remark") > 0 Or InStr(El, "NewOne") > 0 ) Then
Try,
Sub test()
Dim Ws As Worksheet
Dim objList As ListObject
Dim vR(), vDB
Dim sFormula As String
Dim Target As Range
Dim j As Integer
Set Ws = ActiveSheet
Set objList = Ws.ListObjects("TripCost")
vDB = objList.HeaderRowRange
For j = 2 To UBound(vDB, 2) Step 2
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = vDB(1, j)
Next j
sFormula = Join(vR, ",")
Set Target = ActiveCell
With Target.Validation
.Delete
.Add xlValidateList, xlValidAlertStop, xlBetween, sFormula
End With
End Sub

AutoFilter rows when multiple columns are 0 in VBA [duplicate]

I have 8 variables in column A, 1,2,3,4,5 and A, B, C.
My aim is to filter out A, B, C and display only 1-5.
I can do this using the following code:
My_Range.AutoFilter Field:=1, Criteria1:=Array("1", "2", "3","4","5"), _
Operator:=xlFilterValues
But what the code does is it filters variables 1 to 5 and displays them.
I want to do the opposite, but yielding the same result, by filtering out A, B, C and showing variables 1 to 5
I tried this code:
My_Range.AutoFilter Field:=1, Criteria1:=Array("<>A", "<>B", "<>C"), _
Operator:=xlFilterValues
But it did not work.
Why cant I use this code ?
It gives this error:
Run time error 1004 autofilter method of range class failed
How can I perform this?
I think (from experimenting - MSDN is unhelpful here) that there is no direct way of doing this. Setting Criteria1 to an Array is equivalent to using the tick boxes in the dropdown - as you say it will only filter a list based on items that match one of those in the array.
Interestingly, if you have the literal values "<>A" and "<>B" in the list and filter on these the macro recorder comes up with
Range.AutoFilter Field:=1, Criteria1:="=<>A", Operator:=xlOr, Criteria2:="=<>B"
which works. But if you then have the literal value "<>C" as well and you filter for all three (using tick boxes) while recording a macro, the macro recorder replicates precisely your code which then fails with an error. I guess I'd call that a bug - there are filters you can do using the UI which you can't do with VBA.
Anyway, back to your problem. It is possible to filter values not equal to some criteria, but only up to two values which doesn't work for you:
Range("$A$1:$A$9").AutoFilter Field:=1, Criteria1:="<>A", Criteria2:="<>B", Operator:=xlAnd
There are a couple of workarounds possible depending on the exact problem:
Use a "helper column" with a formula in column B and then filter on that - e.g. =ISNUMBER(A2) or =NOT(A2="A", A2="B", A2="C") then filter on TRUE
If you can't add a column, use autofilter with Criteria1:=">-65535" (or a suitable number lower than any you expect) which will filter out non-numeric values - assuming this is what you want
Write a VBA sub to hide rows (not exactly the same as an autofilter but it may suffice depending on your needs).
For example:
Public Sub hideABCRows(rangeToFilter As Range)
Dim oCurrentCell As Range
On Error GoTo errHandler
Application.ScreenUpdating = False
For Each oCurrentCell In rangeToFilter.Cells
If oCurrentCell.Value = "A" Or oCurrentCell.Value = "B" Or oCurrentCell.Value = "C" Then
oCurrentCell.EntireRow.Hidden = True
End If
Next oCurrentCell
Application.ScreenUpdating = True
Exit Sub
errHandler:
Application.ScreenUpdating = True
End Sub
I don't have found any solution on Internet, so I have implemented one.
The Autofilter code with criteria is then
iColNumber = 1
Dim aFilterValueArray() As Variant
Call ConstructFilterValueArray(aFilterValueArray, iColNumber, Array("A", "B", "C"))
ActiveSheet.range(sRange).AutoFilter Field:=iColNumber _
, Criteria1:=aFilterValueArray _
, Operator:=xlFilterValues
In fact, the ConstructFilterValueArray() method (not function) get all distinct values that it found in a specific column and remove all values present in last argument.
The VBA code of this method is
'************************************************************
'* ConstructFilterValueArray()
'************************************************************
Sub ConstructFilterValueArray(a() As Variant, iCol As Integer, aRemoveArray As Variant)
Dim aValue As New Collection
Call GetDistinctColumnValue(aValue, iCol)
Call RemoveValueList(aValue, aRemoveArray)
Call CollectionToArray(a, aValue)
End Sub
'************************************************************
'* GetDistinctColumnValue()
'************************************************************
Sub GetDistinctColumnValue(ByRef aValue As Collection, iCol As Integer)
Dim sValue As String
iEmptyValueCount = 0
iLastRow = ActiveSheet.UsedRange.Rows.Count
Dim oSheet: Set oSheet = Sheets("X")
Sheets("Data")
.range(Cells(1, iCol), Cells(iLastRow, iCol)) _
.AdvancedFilter Action:=xlFilterCopy _
, CopyToRange:=oSheet.range("A1") _
, Unique:=True
iRow = 2
Do While True
sValue = Trim(oSheet.Cells(iRow, 1))
If sValue = "" Then
If iEmptyValueCount > 0 Then
Exit Do
End If
iEmptyValueCount = iEmptyValueCount + 1
End If
aValue.Add sValue
iRow = iRow + 1
Loop
End Sub
'************************************************************
'* RemoveValueList()
'************************************************************
Sub RemoveValueList(ByRef aValue As Collection, aRemoveArray As Variant)
For i = LBound(aRemoveArray) To UBound(aRemoveArray)
sValue = aRemoveArray(i)
iMax = aValue.Count
For j = iMax To 0 Step -1
If aValue(j) = sValue Then
aValue.Remove (j)
Exit For
End If
Next j
Next i
End Sub
'************************************************************
'* CollectionToArray()
'************************************************************
Sub CollectionToArray(a() As Variant, c As Collection)
iSize = c.Count - 1
ReDim a(iSize)
For i = 0 To iSize
a(i) = c.Item(i + 1)
Next
End Sub
This code can certainly be improved in returning an Array of String but working with Array in VBA is not easy.
CAUTION: this code work only if you define a sheet named X because CopyToRange parameter used in AdvancedFilter() need an Excel Range !
It's a shame that Microfsoft doesn't have implemented this solution in adding simply a new enum as xlNotFilterValues ! ... or xlRegexMatch !
Alternative using VBA's Filter function
As an innovative alternative to #schlebe 's recent answer, I tried to use the Filter function integrated in VBA, which allows to filter out a given search string setting the third argument to False. All "negative" search strings (e.g. A, B, C) are defined in an array. I read the criteria in column A to a datafield array and basicly execute a subsequent filtering (A - C) to filter these items out.
Code
Sub FilterOut()
Dim ws As Worksheet
Dim rng As Range, i As Integer, n As Long, v As Variant
' 1) define strings to be filtered out in array
Dim a() ' declare as array
a = Array("A", "B", "C") ' << filter out values
' 2) define your sheetname and range (e.g. criteria in column A)
Set ws = ThisWorkbook.Worksheets("FilterOut")
n = ws.Range("A" & ws.Rows.Count).End(xlUp).row
Set rng = ws.Range("A2:A" & n)
' 3) hide complete range rows temporarily
rng.EntireRow.Hidden = True
' 4) set range to a variant 2-dim datafield array
v = rng
' 5) code array items by appending row numbers
For i = 1 To UBound(v): v(i, 1) = v(i, 1) & "#" & i + 1: Next i
' 6) transform to 1-dim array and FILTER OUT the first search string, e.g. "A"
v = Filter(Application.Transpose(Application.Index(v, 0, 1)), a(0), False, False)
' 7) filter out each subsequent search string, i.e. "B" and "C"
For i = 1 To UBound(a): v = Filter(v, a(i), False, False): Next i
' 8) get coded row numbers via split function and unhide valid rows
For i = LBound(v) To UBound(v)
ws.Range("A" & Split(v(i) & "#", "#")(1)).EntireRow.Hidden = False
Next i
End Sub
An option using AutoFilter
Option Explicit
Public Sub FilterOutMultiple()
Dim ws As Worksheet, filterOut As Variant, toHide As Range
Set ws = ActiveSheet
If Application.WorksheetFunction.CountA(ws.Cells) = 0 Then Exit Sub 'Empty sheet
filterOut = Split("A B C D E F G")
Application.ScreenUpdating = False
With ws.UsedRange.Columns("A")
If ws.FilterMode Then .AutoFilter
.AutoFilter Field:=1, Criteria1:=filterOut, Operator:=xlFilterValues
With .SpecialCells(xlCellTypeVisible)
If .CountLarge > 1 Then Set toHide = .Cells 'Remember unwanted (A, B, and C)
End With
.AutoFilter
If Not toHide Is Nothing Then
toHide.Rows.Hidden = True 'Hide unwanted (A, B, and C)
.Cells(1).Rows.Hidden = False 'Unhide header
End If
End With
Application.ScreenUpdating = True
End Sub
Here an option using a list written on some range, populating an array that will be fiiltered. The information will be erased then the columns sorted.
Sub Filter_Out_Values()
'Automation to remove some codes from the list
Dim ws, ws1 As Worksheet
Dim myArray() As Variant
Dim x, lastrow As Long
Dim cell As Range
Set ws = Worksheets("List")
Set ws1 = Worksheets(8)
lastrow = ws.Cells(Application.Rows.Count, 1).End(xlUp).Row
'Go through the list of codes to exclude
For Each cell In ws.Range("A2:A" & lastrow)
If cell.Offset(0, 2).Value = "X" Then 'If the Code is associated with "X"
ReDim Preserve myArray(x) 'Initiate array
myArray(x) = CStr(cell.Value) 'Populate the array with the code
x = x + 1 'Increase array capacity
ReDim Preserve myArray(x) 'Redim array
End If
Next cell
lastrow = ws1.Cells(Application.Rows.Count, 1).End(xlUp).Row
ws1.Range("C2:C" & lastrow).AutoFilter field:=3, Criteria1:=myArray, Operator:=xlFilterValues
ws1.Range("A2:Z" & lastrow).SpecialCells(xlCellTypeVisible).ClearContents
ws1.Range("A2:Z" & lastrow).AutoFilter field:=3
'Sort columns
lastrow = ws1.Cells(Application.Rows.Count, 1).End(xlUp).Row
'Sort with 2 criteria
With ws1.Range("A1:Z" & lastrow)
.Resize(lastrow).Sort _
key1:=ws1.Columns("B"), order1:=xlAscending, DataOption1:=xlSortNormal, _
key2:=ws1.Columns("D"), order1:=xlAscending, DataOption1:=xlSortNormal, _
Header:=xlYes, MatchCase:=False, Orientation:=xlTopToBottom, SortMethod:=xlPinYin
End With
End Sub
This works for me:
This is a criteria over two fields/columns (9 and 10), this filters rows with values >0 on column 9 and rows with values 4, 7, and 8 on column 10. lastrow is the number of rows on the data section.
ActiveSheet.Range("$A$1:$O$" & lastrow).AutoFilter Field:=9, Criteria1:=">0", Operator:=xlAnd
ActiveSheet.Range("$A$1:$O$" & lastrow).AutoFilter Field:=10, Criteria1:=Arr("4","7","8"), Operator:=xlFilterValues
Okay, I solved it.
I've smashed my head about this problem several times over the years, but I've solved it.
All we need to do is look at all the values that are actually IN the filter range, and if they're not on the list of values we want to filter out, we add them to the "Filter For this item" list.
To note about this code:
I wrote this to act on multiple sheets, and I'm not going to change that as I'm at work and don't have time. I'm sure you can figure it out.
I don't think you need to work in Option base 1... But I am, so if you run into issues... might be that.
Despite how many hundreds of thousands of times it's checking and rechecking the same arrays, it's remarkably fast.
I'm sure there is a way to redim KeepArray, but I didn't have time to consider it.
Option Explicit
Option Base 1
Sub FilterTable()
Dim WS As Worksheet
Dim L As Long
Dim I As Long
Dim N As Long
Dim tbl As ListObject
Dim tblName As String
Dim filterArray
Dim SrcArray
Dim KeepArray(1 To 5000) ' you might be able to figure out a way to redim this easiely later on.. for now I'm just oversizing it.
N = 0
filterArray = Array("FilterMeOut007", _
"FilterMeOut006", _
"FilterMeOut005", _
"FilterMeOut004", _
"FilterMeOut003", _
"FilterMeOut002", _
"FilterMeOut001")
For Each WS In ThisWorkbook.Worksheets
Debug.Print WS.Name
If Left(WS.Name, 4) = "AR -" Then
With WS
tblName = Replace(WS.Name, " ", "_")
Set tbl = WS.ListObjects(tblName)
SrcArray = tbl.ListColumns(1).DataBodyRange
For I = 1 To UBound(SrcArray, 1)
If Not ExistsInArray(KeepArray, SrcArray(I, 1)) _
And Not ExistsInArray(filterArray, SrcArray(I, 1)) Then
N = N + 1
KeepArray(N) = SrcArray(I, 1)
End If
Next I
tbl.DataBodyRange.AutoFilter Field:=1, Criteria1:=KeepArray, Operator:=xlFilterValues
End With
End If
Next WS
End Sub
Function ExistsInArray(arr, Val) As Boolean
Dim I As Long
ExistsInArray = False
For I = LBound(arr) To UBound(arr)
If arr(I) = Val Then
ExistsInArray = True
Exit Function
End If
Next I
End Function
Please let me know if you run into any errors with this as I'd like to stress test and debug it as much as possible in the future to make it as portable as possible. I picture using it a lot.
Please check this one for filtering out values in a range; It works.
Selection.AutoFilter field:=33, Criteria1:="<>Array(IN1R,IN2R,INDA)", Operator:=xlFilterValues
Actually, the above code did not work. Hence I give a loop to hide the entire row whenever the active cell has the value that I am searching for.
For each cell in selection
If cell.value = “IN1R” or cell.value = “INR2” or cell.value = “INDA” then
Else
Activecell.Entirerow.Hidden = True
End if
Next

EXCEL: highlighting reoccuring data in the same column

I have a column(D) of data in Excel that has been sorted using:
=TEXT(B2,"###").
This is to show a list of data (numberical) that has an additional "REP 1" against it.
Not all data has a "REP 1" in there, so I would like to highlight all fields which contain BOTH the number and the "REP 1".
I could highlight all "REP 1" fields, and see if there is a duplicate before it, but this is just a sample sheet. I have over 8,000+ fields to go through, and would be too time consuming.
Please see the below link for the example:
Required Formatting
I hope this all makes sense.
Thanks,
Tim.
Not sure if its possible to do with conditional formatting but this VBA code should work. Your Data wouldn't have to be sorted in any particular order, and assumes the data you are formatting is in column D. I've tested on a few 100 rows and it works fine, so should be fine with a large data set. Ive tried to explain what the code is doing through the comments in the code.
Sub formatCells()
Dim x As Variant
Dim y As Variant
Dim searchval As String
Dim a As Variant
Dim lastrow As Long
Dim rng As Range
Application.ScreenUpdating = False ' turn off screen updates
lastrow = Cells(Rows.Count, 4).End(xlUp).Row 'find the last blank cell
x = 2 'set rownumber
y = 4 'set columnnumber
While Cells(x, y) <> "" ' create loop
If InStr(Cells(x, y), "REP1") Then 'search for string in cell
Cells(x, y).Interior.Color = RGB(255, 0, 0) 'if string exists fill cell
End If
x = x + 1 ' loop
Wend ' end loop
x = 2 ' reset row number
y = 4 ' reset column number
While Cells(x, y) <> "" ' create loop 2
If Cells(x, y).Interior.Color = RGB(255, 0, 0) And InStr(Cells(x, y), "REP1") Then 'if cells is red and contains Rep1
a = Cells(x, y).Value ' set a to equal the cell that is red and and contains REP1
searchval = Left(a, Len(a) - 5) 'remove space and REP1 and set value ready for search
If searchval <> "" Then 'if theres a search value available run steps below
With Range("D1:D" & lastrow) 'set range to be column A
Set rng = .Find(What:=searchval, _
After:=.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False)
If Not rng Is Nothing Then 'If search value is found
Application.Goto rng, True ' go to cell
ActiveCell.Interior.Color = RGB(255, 0, 0) 'set cell to red
End If
End With
End If
End If
x = x + 1 'loop 2
Wend ' end loop 2
End Sub
EDIT - Looks at column B not D
Sub formatCells()
Dim x As Variant
Dim y As Variant
Dim searchval As String
Dim a As Variant
Dim lastrow As Long
Dim rng As Range
Application.ScreenUpdating = False ' turn off screen updates
lastrow = Cells(Rows.Count, 2).End(xlUp).Row 'find the last blank cell
x = 2 'set rownumber
y = 2 'set columnnumber
While Cells(x, y) <> "" ' create loop
If InStr(Cells(x, y), "REP1") Then 'search for string in cell
Cells(x, y).Interior.Color = RGB(255, 0, 0) 'if string exists fill cell
End If
x = x + 1 ' loop
Wend ' end loop
x = 2 ' reset row number
y = 2 ' reset column number
While Cells(x, y) <> "" ' create loop 2
If Cells(x, y).Interior.Color = RGB(255, 0, 0) And InStr(Cells(x, y), "REP1") Then 'if cells is red and contains Rep1
a = Cells(x, y).Value ' set a to equal the cell that is red and and contains REP1
searchval = Left(a, Len(a) - 5) 'remove space and REP1 and set value ready for search
If searchval <> "" Then 'if theres a search value available run steps below
With Range("B1:B" & lastrow) 'set range to be column A
Set rng = .Find(What:=searchval, _
After:=.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False)
If Not rng Is Nothing Then 'If search value is found
Application.Goto rng, True ' go to cell
ActiveCell.Interior.Color = RGB(255, 0, 0) 'set cell to red
End If
End With
End If
End If
x = x + 1 'loop 2
Wend ' end loop 2
End Sub

Variables in Conditional Formatting Formula1

I'm trying to use variables in the FormatCondition Formula1 property. The variables will be cell references. However, I can't get the syntax right. The two bits I'm having trouble with in the code below are: "=(C$3:J$10=""CM"")" and "=($C3:$J10=""RM"")".
The aim of this is to highlight a column with CM in a certain cell, and to highlight a row with RM in a certain cell. The number of columns and rows will increase and decrease, hence the use of variables.
Or if this isn't the right way or the best way, alternatives would be appreciated.
The code is:
Private Sub Workbook_Open()
Application.ScreenUpdating = False
'Rows
Dim iRowA As Integer, iRowB As Integer, iRowC As Integer
Dim iRowDataStart As Integer, iRowLast As Integer
'Columns
Dim iColX As Integer, iColY As Integer, iColZ As Integer
Dim iColDataStart As Integer, iColLast As Integer
'Ranges
Dim rAll As Range
Dim rRowB As Range, rColY As Range
Dim rRowMark As Range, rColMark As Range
'String
Dim sString As String
'Assign values, normally these would be variable values, not assigned
iRowA = 1: iRowB = 2: iRowC = 3
iRowDataStart = 4: iRowLast = 10
iColX = 1: iColY = 2: iColZ = 3
iColDataStart = 4: iColLast = 10
'Set ranges
Set rAll = Range(Cells(iRowA, iColX), Cells(iRowLast, iColLast))
Set rRowB = Range(Cells(iRowB, iColZ), Cells(iRowLast, iColLast))
Set rColY = Range(Cells(iRowC, iColY), Cells(iRowLast, iColLast))
Set rRowMark = Range(Cells(iRowC, iColZ), Cells(iRowLast, iColLast))
Set rColMark = Range(Cells(iRowC, iColZ), Cells(iRowLast, iColLast))
'Delete all CF currently in the worksheet
With rAll
.FormatConditions.Delete
End With
'Format column with Column Mark
sString = "=(C$3:J$10=""CM"")"
With rRowB
.FormatConditions.Add _
Type:=xlExpression, _
Formula1:=sString
.FormatConditions(.FormatConditions.Count).SetFirstPriority
With .FormatConditions(1)
.Interior.Color = RGB(196, 189, 151)
.StopIfTrue = False
End With
End With
'Format row with Row Mark
sString = "=($C3:$J10=""RM"")"
With rColY
.FormatConditions.Add _
Type:=xlExpression, _
Formula1:=sString
.FormatConditions(.FormatConditions.Count).SetFirstPriority
With .FormatConditions(1)
.Font.ColorIndex = 2
.Interior.Color = RGB(127, 127, 127)
.StopIfTrue = False
End With
End With
Range("A1").Select
Application.StatusBar = False
Application.CutCopyMode = False
End Sub
You just need to dynamically set your ranges by getting last row and column of your data where you can find many examples here like this one. Something like:
Dim r As Range
Dim lr As Long, lc As Long
Dim formula As String
With Sheet1 '~~> change to your actual sheet
lr = .Range("C" & .Rows.Count).End(xlUp).Row '~~> based on C, adjust to suit
lc = .Cells(3, .Columns.Count).End(xlToLeft).Column '~~> based on row 3
Set r = .Range(.Cells(3, 3), .Cells(lr, lc))
formula = "=(" & r.Address & "=""CM"")"
'~~> formatting code here
End With
Or you can try what I've posted here about Conditional Formatting which of course can be automated as I posted HERE and HERE. Something like:
formula = "=C3=""CM"""
[C3].FormatConditions.Add xlExpression, , formula
With [C3].FormatConditions(1)
.Interior.Color = RGB(196, 189, 151)
.ModifyAppliesToRange r
End With
HTH.

Resources