Insert a cell location in condition formatting - excel

so I have a code like this:
Sub ApplyIconSets()
Dim rng As Range
Dim iset As IconSetCondition
Set rng = Application.InputBox("Select a Range", "Obtained Range Object", Type:=8)
rng.Name = "selected"
LastRow = Range("selected").Rows.Count
LastColumn = Range("selected").Columns.Count
With Range("selected")
For i = 2 To LastColumn
For r = 1 To LastRow
Set iset = .Cells(r, i).FormatConditions.AddIconSetCondition
With iset
.IconSet = ActiveWorkbook.IconSets(xl3Arrows)
.ReverseOrder = False
.ShowIconOnly = False
End With
With iset.IconCriteria(2)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = Range("selected").Cells(r, i).Offset(, -1)
End With
With iset.IconCriteria(3)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = Range("selected").Cells(r, i).Offset(, -1)
End With
Next r
Next i
End With
End Sub
So basically this code applies IconSet condition formatting to a cell based on its preceding cell value. The code works perfectly fine, however there's one point I want to improve it.
When I check the condition applied, the code inputs the preceding cell absolute value instead of the cell's location.
Like This
However, I want the code to input the cell location, so that when I change the data, it still works instead of having me rerun the code.
Like this
I have tried to change
.Value = Range("selected").Cells(r,i).Offset(,-1).Address
But it returns a string, hence the condition won't work.
Anyone knows a solution?
Thanks in advance.

I'm not sure you've got your conditional format logic right. Your code as it stands will never show an amber arrow because IconCriteria(3) will evaluate first. Since IconCriteria(2) has identical property values, it will never be met. If you wanted the green arrow to display for numbers greater than the tested cell value and the amber arrow to display for numbers equal to the value, then you'd need to write the code as given below.
I'm also wondering if a ForEach loop might be simpler, especially as it would pass the cell range itself so you could just derive your workbook and worksheet objects from that. This would avoid a potential problems of your unqualified ranges. You'd simply need to add an If statement to ensure that you didn't try to offset past column 1.
All in all, then, the code below might serve your purposes. BTW I'd recommend adding Option Explicit at the top of your module and handling the case where the user hits Cancel on the input box:
Option Explicit
Sub ApplyIconSets()
Dim sel As Range, cell As Range
' Acquire the target range and handle a cancelled input box.
On Error GoTo Canx
Set sel = Application.InputBox("Select a Range", "Obtained Range Object", Type:=8)
' Iterate cell by cell to add the condition.
On Error GoTo EH
For Each cell In sel.Cells
'Ignore the cell if it is in column 1.
If cell.Column > 1 Then
With cell.FormatConditions
'Delete existing conditions.
.Delete
'Add a new condition.
With .AddIconSetCondition
.IconSet = cell.Worksheet.Parent.IconSets(xl3Arrows)
'Set the amber criterion.
'Note: we have to use '>=' but anything '>' will be caught
'in the green operator, so only '=' will meet this criterion.
With .IconCriteria(2)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = "=" & cell.Worksheet.Name & "!" & cell.Offset(, -1).Address
End With
'Set the green criterion.
'Note: we have to use just '>' because this is evaluated first
'and '>=' would result in amber never capturing a value.
With .IconCriteria(3)
.Type = xlConditionValueFormula
.Operator = xlGreater
.Value = "=" & cell.Worksheet.Name & "!" & cell.Offset(, -1).Address
End With
End With
End With
End If
Next
Exit Sub
Canx:
Debug.Print "User cancelled."
Exit Sub
EH:
Debug.Print Err.Number; Err.Description
End Sub

Please try to define in each segment, the value as a reference to the cell like this:
With iset.IconCriteria(3)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = "=Sheet1!$B$1"
End With
You'd have to replace the string with variables to something like this:
.Value = "=Sheet1!" & Range("selected").Cells(r, i).Offset(, -1).Address
Please mark this answer if this helped you.

This code does what you want.
Sub ApplyIconSets()
Dim LastRow As Long, LastColumn As Long
Dim Rng As Range
Dim iSet As IconSetCondition
Dim i As Integer, R As Integer
Set Rng = Application.InputBox("Select a Range", "Obtained Range Object", Type:=8)
Rng.Name = "selected"
LastRow = Range("selected").Rows.Count
LastColumn = Range("selected").Columns.Count
With Range("selected")
For i = 1 To LastColumn
For R = 1 To LastRow
Set iSet = .Cells(R, i).FormatConditions.AddIconSetCondition
With iSet
.IconSet = ActiveWorkbook.IconSets(xl3Arrows)
.ReverseOrder = False
.ShowIconOnly = False
End With
With iSet.IconCriteria(2)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = "=" & Range("selected").Cells(R, i).Offset(, -1).Address
End With
With iSet.IconCriteria(3)
.Type = xlConditionValueFormula
.Operator = xlGreaterEqual
.Value = "=" & Range("selected").Cells(R, i).Offset(, -1).Address
End With
Next R
Next i
End With
End Sub
I didn't want to spend the time experimenting further: I think that the condition can be set for the entire range in one go, causing Excel to set relative formatting instead of absolute. You may like to try that. There would be a difference in speed. You may also like to add code which deletes existing CF before applying the new. CF gets overloaded easily and will then slow down your sheet.

Related

in Excel VBA why does my code not work with SpecialCells type visible and work without it?

In columns Bk and CB they both contain formula's that will result in a code. Now CB will also contain four codes and a remove statement which if they match with the cell in column BK in the same row then take the value from CB and paste over hence overriding the value in BK with that code and then paste it red.
the above should be done only on a filtered range though.
The ignore #N/A are in there as the overide column will error out on almost everyline except for when there is a code to overide.
This macro works perfectly without the visible cells statement at the end of my with range line but as soon as the visible cells statement is added the loop only goes up to #N/A and disregards the rest of the ElseIF statement.
Here is my code below:
Option Explicit
Sub Override()
Dim x As Workbook: Set x = ThisWorkbook
Dim rRange As Variant, fltrdRng As Range, aCell As Range, rngToCopy As Range
Dim ws As Worksheet
Dim LR As Long
Dim LR2 As Long
Dim SrchRng As Range, cel As Range
Dim mRow
mRow = 2
Set ws = x.Worksheets("Data")
LR = ws.Range("CB" & ws.Rows.Count).End(xlUp).Row
LR2 = ws.Range("BK" & ws.Rows.Count).End(xlUp).Row
'clears any filters on the sheet
ws.AutoFilterMode = False
' turns formula's to manual
Application.Calculation = xlManual
'copies down the formula in Column BK ignoring the last two rows as they have already been pasted over.
ws.Range("BK2:BK4 ").AutoFill Destination:=ws.Range("BK2:BK" & LR2 - 2)
'filters on N/A's and 10 as these are the codes we are interested in overiding
ws.Range("$A$1:$CB$1").AutoFilter Field:=19, Criteria1:=Array( _
"10", "N/A"), Operator:= _
xlFilterValues
' will loop through all cells in specified range and ignore any error's and #N/A's and will paste over the code overided in CB column to the BK column if conditions are met.
On Error Resume Next
While IsEmpty(ws.Range("CB" & mRow)) = False
With ws.Range("CB" & mRow).SpecialCells(xlCellTypeVisible)
If .Value = "#N/A" Then
ElseIf .Value = "1234" Then
.Offset(0, -17).Value = "1234"
.Offset(0, -17).Interior.Color = vbRed
ElseIf .Value = "1235" Then
.Offset(0, -17).Value = "1235"
.Offset(0, -17).Interior.Color = vbRed
ElseIf .Value = "1236" Then
.Offset(0, -17).Value = "1236"
.Offset(0, -17).Interior.Color = vbRed
ElseIf .Value = "Remove" Then
.Offset(0, -17).Value = "Remove"
.Offset(0, -17).Interior.Color = vbRed
ElseIf .Value = "1237" Then
.Offset(0, -17).Value = "1237"
.Offset(0, -17).Interior.Color = vbRed
End If
End With
mRow = mRow + 1
Wend
'turn Formula 's back to automatic
Application.Calculation = xlAutomatic
End Sub
With ws.Range("CB" & mRow).SpecialCells(xlCellTypeVisible)
Using SpecialCells on just one cell is problematic.
Instead, use it on the entire filtered column, like this, which will replace your entire While...Wend loop (by the way, While...Wend is obsolete):
On Error Resume Next
Dim visibleCells As Range
Set visibleCells = ws.Range("CB2:CB" & LR).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If visibleCells Is Nothing Then Exit Sub
Dim cell As Range
For Each cell In visibleCells
If Not IsError(cell.Value) Then
Select Case cell.Value
Case "1234", "1235", "1236", "1237", "Remove"
cell.Offset(0, -17).Value = cell.Value
cell.Offset(0, -17).Interior.Color = vbRed
End Select
End If
Next

Hiding row if cell equals next visible cell

I am trying to write a macro that hides the row if the cell value equals the next visible cell in that column and loops through the whole column. I have read that SpecialCells(xlCellTypeVisible) only works up to 8192 cells and my spreadsheet has 15,000 rows.
I have tried something like this but want to restrict it to only visible cells
Sub Test()
For i = 7 To 15258
If Range("P" & i).Value = Range("P" & i + 1).Value Then
Rows(i).Hidden = True
End If
Next i
End Sub
I have tried to search for a solution but haven't been able to find one yet.
Thanks!
I'd be surprised if this couldn't be optimized just a little bit, but it will work for what you are needing.
You can follow the comments within the code itself to kind of get a sense of what it's doing, but in a nutshell, you are using a For...Next statement to loop through your visible cells. For each visible cell, you will search for the next visible cell and then check to see if that matches. If it does, you add that cell to a special range that tracks all the rows to hide at the end of the code, then hide it.
Sub Test()
Dim ws As Worksheet, lookupRng As Range, rng As Range, lstRow As Long
Set ws = ThisWorkbook.Worksheets(1)
lstRow = 15258
Set lookupRng = ws.Range("P7:P" & lstRow)
Dim rngToHide As Range, i As Long
For Each rng In lookupRng.SpecialCells(xlCellTypeVisible)
Application.StatusBar = "Checking row " & rng.Row & " for matches."
For i = rng.Row + 1 To lstRow 'Loop through rows after rng
If Not ws.Rows(i).Hidden Then 'Check if row is hidden
If rng.Value = ws.Cells(i, "P") Then 'check if the non-hidden row matches
If rngToHide Is Nothing Then 'Add to special range to hide cells
Set rngToHide = ws.Cells(i, "P")
Else
Set rngToHide = Union(rngToHide, ws.Cells(i, "P"))
End If
End If
Exit For 'Exit the second For statement
End If
Next i
Next rng
Application.StatusBar = "Hiding duplicate rows"
If Not rngToHide Is Nothing Then rngToHide.EntireRow.Hidden = True
Application.StatusBar = False
End Sub

Loop through all font colored cells in a range

I extracted the data according to ciriteria and marked them as blue. I'm looking for help with a Macro which would loop through all font colored cells (Blue) in a range.
I want to use only font colored cells in a range and mark in different color. And Msgbox show data that meet the criteria.
I had trouble finding information on looping through cells which contain only a specified colour. Anyone know how this could be done?
Dim i As Long
Dim LastRow As Integer
LastRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
Msg = "Data:"
For i = 1 To LastRow
If Cells(i + 1, 2).Value - Cells(i, 2).Value <> 0 Then
Cells(i, 2).Font.Color = vbBlue
Cells(i, 1).Font.Color = vbBlue
For Each Cell In Range("A:B")
If Cells(i, 1).Font.Color = vbBlue And Cells(i + 1, 1).Value - Cells(i, 1).Value > 4 Then
Cells(i, 2).Font.Color = vbGreen
Cells(i, 1).Font.Color = vbGreen
End If
Next
Msg = Msg & Chr(10) & i & " ) " & Cells(i, 2).Value & " : " & " --> " & Cells(i, 1).Value
End If
Next i
MsgBox Msg, vbInformation
There are multiple issues with your code:
Your loops are nested. You are searching through all the data every time you prepare one line. ==> Move the inner loop behind the loop you're coloring in.
The result message Msg = Msg & Chr(10) & i is constructed outside of the If Cells(i, 1).Font.Color = vbBlue And... condition, meaning that every line will be written into the result String. Move this part inside the 2nd loop, and the string should be contain only blue lines.
Also, please don't loop through For Each Cell In Range("A:B"). This will examine every cell in those columns, way beyond those who contain actual data. Use LastRow as in the first loop.
I believe you should be able to use the Find function to do this....
For example, select some cells on a sheet then execute:
Application.FindFormat.Interior.ColorIndex = 1
This will colour the cells black
Now execute something like:
Debug.Print ActiveCell.Parent.Cells.Find(What:="*", SearchFormat:=True).Address
This should find those cells. So you should be able to define your required Font with the FindFormat function.
BTW, make sure to test to see if the range returned is nothing for the case where it cant find any matches..
Hope that helps.
Edit:
The reason I would use the find method is because your code checks each cell in two columns. The Find method should be much quicker.
You will need to have a Do - While loop to find all cells in a range which is common with the Find function in VBA.
If you run this function, it should debug the address of any font matches that you are looking for - for a particular sheet. This should give you the idea...
Sub FindCells()
Dim rData As Range, rPtr As Range
Set rData = ActiveSheet.Range("A:B")
Application.FindFormat.Clear
Application.FindFormat.Font.Color = vbBlue
Set rPtr = rData.Find(What:="*", SearchFormat:=True)
If Not rPtr Is Nothing Then
Debug.Print rPtr.Address
End If
Application.FindFormat.Clear
Application.FindFormat.Font.Color = vbGreen
Set rPtr = rData.Find(What:="*", SearchFormat:=True)
If Not rPtr Is Nothing Then
Debug.Print rPtr.Address
End If
End Sub
Ok then - sorry keep getting distracted..
This code will search for cells with your fonts for a particular data range.
I believe you just need to implement your logic into the code...
Option Explicit
Public Sub Test()
Dim rData As Range
Set rData = Sheet1.Range("A:B")
Call EnumerateFontColours(rData, vbBlue)
Call EnumerateFontColours(rData, vbGreen)
End Sub
Public Sub EnumerateFontColours(ByVal DataRange As Range, ByVal FontColour As Long)
Dim rPtr As Range
Dim sStartAddress As String
Dim bCompleted As Boolean
Application.FindFormat.Clear
Application.FindFormat.Font.Color = FontColour
Set rPtr = DataRange.Find(What:="*", SearchFormat:=True)
If Not rPtr Is Nothing Then
sStartAddress = rPtr.Address
Do
'**********************
Call ProcessData(rPtr)
'**********************
Set rPtr = DataRange.Find(What:="*", After:=rPtr, SearchFormat:=True)
If Not rPtr Is Nothing Then
If rPtr.Address = sStartAddress Then bCompleted = True
Else
bCompleted = True
End If
Loop While bCompleted = False
End If
End Sub
Public Sub ProcessData(ByVal r As Range)
Debug.Print r.Address
End Sub

VBA: Finding first empty cell in a selection of ranges

So i'm stuck again with my limited knowledge of VBA script.
I have this script:
Sub new_Group()
Dim rnTarget As Range
Set rnTarget = Blad2.Cells(Range("A9:A44").Cells.Count, 1).End(xlUp)
If rnTarget <> "" Then Set rnTarget = rnTarget.Offset(1)
Blad1.Range("A7:F15").Copy
Blad2.Range(rnTarget.Address).PasteSpecial Paste:=xlPasteValues
Blad2.Range(rnTarget.Address).PasteSpecial Paste:=xlPasteFormats
Blad2.Range("P8").Value = Blad2.Range("P8").Value + 10
End Sub
It finds the first empty cell in the range of A9 to A44 and copys a premade number of cells in its place.
My question now is if it's possible to have it start looking in another range if no cell is empty in A9:A44?
I want it to first check from A9 to A44 for empty cells, if none is found then start looking in range H9 to H44 and so on.
I have tried:
Set rnTarget = Blad2.Cells(Range("A9:A44, H9:H44").Cells.Count, 1).End(xlUp)
But then it just kept going down the A column.
Do i have to make an Else in some way?
Sub new_Group()
If (new_asd("A") = True) Then
new_asd ("H")
End If
End Sub
Function new_asd(new_col As String) As Boolean
Dim rnTarget As Range
new_asd = False
If Blad2.Range(new_col & "44") = "" Then
Set rnTarget = Blad2.Cells(Range(new_col & "9:" & new_col & "44").Cells.Count, Range(new_col & 1).Column).End(xlUp)
If rnTarget <> "" Then
Set rnTarget = rnTarget.Offset(1)
Blad1.Range("A7:F15").Copy
Blad2.Range(rnTarget.Address).PasteSpecial Paste:=xlPasteValues
Blad2.Range("P8").Value = Blad2.Range("P8").Value + 10
Else
new_asd = True
End If
Else
new_asd = True
End If
End Function

Iterative SUMIF Function Using VBA

Consider the following table:
What I would like to be able to do is create something like on the right hand side. This essentially requires telling Excel to sum all values for which the cell is zero until it encounters a 1, at which point it should begin the count again. I imagine this can be done using VBA, so I just need to determine how to actually set up that code. I imagine that the building blocks should be something like this:
Dim row As Long
Dim sum As List
row = Excel row definition
While ColB <> ""
If ColB.value = 0
Append ColC.value to Sum
Else Do Nothing
row = row + 1
Loop
Any help with the structure and syntax of the code would be much appreciated.
Try this:
Sub test()
Dim cel As Range, sRng As Range, oRng As Range, Rng As Range
Dim i As Long: i = 1
On Error GoTo halt
With Sheet1
.AutoFilterMode = False
Set Rng = .Range("B1", .Range("B" & .Rows.Count).End(xlUp))
Rng.AutoFilter 1, 0
Set sRng = Rng.Offset(1, -1).Resize(Rng.Rows.Count - 1) _
.SpecialCells(xlCellTypeVisible)
Rng.AutoFilter 1, 1
Set oRng = Rng.Offset(1, 0).SpecialCells(xlCellTypeVisible)
.AutoFilterMode = False
End With
If sRng.Areas.Count >= oRng.Areas.Count Then i = 2
For Each cel In oRng.Areas
If i > sRng.Areas.Count Then Exit For
If cel.Cells.Count = 1 Then
cel.Offset(0, 1).Formula = _
"=SUM(" & sRng.Areas(i).Address(True, True) & ")"
Else
cel.Cells(cel.Cells.Count).Offset(0, 1).Formula = _
"=SUM(" & sRng.Areas(i).Address(True, True) & ")"
End If
i = i + 1
Next
Exit Sub
halt:
Sheet1.AutoFilterMode = False
End Sub
Edit1:
Above works regardless of how many zero's or one's you have in Column B.
If error occurs, it will exit. I leave the coding on how you want the error handled.

Resources