Conditional formatting using VBA macros
Conditional formatting in Excel is a smart way to highligt cells that meet certain criteria, e.g. values above a certain limit.
However counting the cells afterwards is very difficult, unless you do it manually, so in some cases it can be better to use VBA macros instead.
A colleague of mine has a spreadsheet with quality and process data on a daily basis, 24 rows and X columns for each day. He used Excel's conditional formatting to paint the cells red if so and so.
The other day he asked, if I could write a macro that counted the red cells in each column. Ignorantly I said: "Yes, of course" and thought it would be an easy job. I was wrong!
It is very complicated (if possible), and it turned out to be a lot easier to put his conditions into a macro and let VBA do the formatting.
There is an example below, and you can download a zip compressed workbook with a table and the code here.
You can do it in many ways, but in this example the macro reads:
- If a column is to be formatted
- Low and/or high limit for each column
- Optional: Control values - e.g. runtime
It then loops through the columns, and if a cell value is below or above a limit, it gets a red background colour.
The sample table looks like this:
If you leave a limit cell empty, the limit will be ignored (not tested). That gives 4 scenarios to test:
Low limit = True and High limit = True
Low limit = False and High limit = False
Low limit = False and High limit = True
Low limit = True and High limit = False
Also, if you define a column with control values (here: runtime), outliers will be ignored if the check value isn't = 1.
The outliers for each column are counted.
If you expand the table to the right, the macro will find out and include new columns.
You can select the code below with the mouse, copy it (CTRL+C) and paste it into a VBA module (CTRL+V) or download the zip compressed Excel workbook with the example.
The code uses ranges and nested For Each Next loops, and there is also a test to check if a value is a whole number (Integer) or not.
Nothing stops you from adding an optional LowLow and HighHigh limit, but then the number of scenarios to test will increase from 4 to 16.
Let's go:
Sub FormatCells()
Dim bL As Boolean
Dim bH As Boolean
Dim bRed As Boolean
Dim bCheck As Boolean
Dim lCols As Long
Dim lActiveCol As Long
Dim lOffset As Long
Dim lRedCount As Long
Dim dHigh As Double
Dim dLow As Double
Dim rMode As Range
Dim rCol As Range
Dim rMCell As Range
Dim rCell As Range
On Error GoTo ErrorHandle
Application.ScreenUpdating = False
Set rMode = Range("A1").CurrentRegion
lCols = rMode.Columns.Count - 1
Set rMode = Range(Range("B26"), Range("B26").Offset(0, lCols - 1))
For Each rMCell In rMode
lActiveCol = lActiveCol + 1
With rMCell
If .Value = "CF" Then
bCheck = False
lRedCount = 0
If IsNumeric(.Offset(1, 0).Value) And .Offset(1, 0).Value <> 0 Then
lOffset = CLng(.Offset(1, 0).Value)
If Abs(.Offset(1, 0).Value / lOffset) = 1 Then
If lOffset > 0 And lOffset + lActiveCol <= lCols Then
bCheck = True
ElseIf lOffset < 0 And lActiveCol + lOffset > 0 Then
bCheck = True
Else
MsgBox "The column for check values is not " & _
"in the table. Check your offset value in cell " & .Address
End If
Else
MsgBox "Column offset must be a whole number." & _
.Offset(1, 0).Value & " is ignored."
End If
End If
If IsEmpty(.Offset(3, 0)) = False And _
IsNumeric(.Offset(3, 0).Value) Then
dLow = .Offset(3, 0).Value
bL = True
Else
bL = False
End If
If IsEmpty(.Offset(4, 0)) = False And _
IsNumeric(.Offset(4, 0).Value) Then
dHigh = .Offset(4, 0).Value
bH = True
Else
bH = False
End If
If bL = False And bH = False Then GoTo Skip
Set rCol = Range(.Offset(-1, 0), .Offset(-24, 0))
If bL And bH Then
For Each rCell In rCol
bRed = False
With rCell
If .Value < dLow Or .Value > dHigh Then
If bCheck And .Offset(0, lOffset).Value = 1 Then
bRed = True
ElseIf bCheck = False Then bRed = True
End If
End If
If bRed Then
.Interior.ColorIndex = 3
.Interior.Pattern = xlSolid
lRedCount = lRedCount + 1
Else
.Interior.ColorIndex = xlNone
End If
End With
Next
End If
If bL And bH = False Then
For Each rCell In rCol
bRed = False
With rCell
If .Value < dLow Then
If bCheck And .Offset(0, lOffset).Value = 1 Then
bRed = True
ElseIf bCheck = False Then bRed = True
End If
End If
If bRed Then
.Interior.ColorIndex = 3
.Interior.Pattern = xlSolid
lRedCount = lRedCount + 1
Else
.Interior.ColorIndex = xlNone
End If
End With
Next
End If
If bL = False And bH Then
For Each rCell In rCol
bRed = False
With rCell
If .Value > dHigh Then
If bCheck And .Offset(0, lOffset).Value = 1 Then
bRed = True
ElseIf bCheck = False Then bRed = True
End If
End If
If bRed Then
.Interior.ColorIndex = 3
.Interior.Pattern = xlSolid
lRedCount = lRedCount + 1
Else
.Interior.ColorIndex = xlNone
End If
End With
Next
End If
Else
GoTo Skip
End If
.Offset(2, 0).Value = lRedCount
End With
Skip:
Next
BeforeExit:
Set rMode = Nothing
Set rCol = Nothing
Set rMCell = Nothing
Set rCell = Nothing
Application.ScreenUpdating = True
Exit Sub
ErrorHandle:
MsgBox Err.Description & " Procedure FormatCells"
Resume BeforeExit
End Sub
That's it - a way of using VBA for conditional formatting and counting cells that are formatted conditionally.
Related:
|