In financial models it is common to colour-code the cells based on their input (see here for example). I would like to create a macro that automates this task for me.
The required colour-code is the following
Blue: Constants (except text)
Black: Formulas
Green: References to other sheets
Red: References to separate files or external links
Thanks to the great answers of Rory and Samuel I was able to achieve the above with the following code:
Sub financial_color_coding()
' Color hard-coded cells blue
With Selection.SpecialCells(xlCellTypeConstants, 21).Font
.Color = -65536 ' colour selected cells blue
.TintAndShade = 0
End With
' Select cells that contain formulas
Selection.SpecialCells(xlCellTypeFormulas, 23).Select
'Color selected cells based on their input
For Each cell In Selection
If Left(cell.Formula & " ", 1) = "=" Then
If InStr(CleanStr(cell.Formula), "]") Then
cell.Font.Color = RGB(255, 0, 0) ' red for references to other files
ElseIf InStr(CleanStr(cell.Formula), "!") Then
cell.Font.Color = RGB(0, 150, 0) ' green for references to other sheets
Else
cell.Font.Color = RGB(0, 0, 0) 'black for every other formula
End If
End If
Next cell
End Sub
Function CleanStr(strIn As String) As String
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "\""[^)]*\"""
.Global = True
CleanStr = .Replace(strIn, vbNullString)
End With
End Function
Running the marco will only change the font of cells in a workbook that contain either a constant number or a formula and will keep the overall formatting of the text unchanged.
SpecialCells are documented here:
https://learn.microsoft.com/en-us/office/vba/api/excel.range.specialcells
However, not all can be done with it. If a formula contains a ! or ] it references to another sheet or file. The CleanStr removes all text in quotes, since these text may also include these characters.
Selection.SpecialCells(xlCellTypeConstants).Font.Color = RGB(0, 0, 255) 'blue for constant
Selection.SpecialCells(xlCellTypeFormulas).Font.Color = RGB(0, 0, 0) 'black for formulas
'to be more specifiy
For Each cell In Selection
If Left(cell.Formula & " ", 1) = "=" Then
If InStr(CleanStr(cell.Formula), "]") Then
cell.Font.Color = RGB(255, 0, 0) ' red for references to other files
ElseIf InStr(CleanStr(cell.Formula), "!") Then
cell.Font.Color = RGB(0, 150, 0) ' green for references to other sheets
Else
cell.Font.Color = RGB(250, 0, 255) 'pink for formulars with output text
End If
ElseIf Not IsNumeric(cell.Text) Then
cell.Font.Color = RGB(0, 0, 0) 'black for text constant
End If
Next cell
The CleanStr is adopted from here: Remove text that is between two specific characters of a string
Function CleanStr(strIn As String) As String
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "\""[^)]*\"""
.Global = True
CleanStr = .Replace(strIn, vbNullString)
End With
End Function
Related
If I have a column of referenced numbers and I would like to color the referenced cell automatically after right clicking, how can I refer to the reference cell in VBA. In the exmaple I have the referenced values in column [O]. At [O4] I have the value [=$G$12]. If I click on [O4] I want to color it together with the reference cell G12. It would be an additional step that I would like to color the cell above [G12] as well.
I tried to insert this in the formula:
REPLACE(SUBSTITUTE(FORMULATEXT($O$4),"$",""),1,1,"")
Which would give back the reference cell [G12] in a normal excel sheet without equal sign but the script doesn't work with it.
Thank you for the support in advance!
Select Case Target.Address
Case "$O$4" '<~ if cell A1 is clicked, highlight cells C5-C9 yellow
Cancel = True
If .Range("O4,G11:G12").Interior.Color = RGB(255, 153, 0) Then
.Range("O4,G11:G12").Interior.Color = RGB(255, 255, 255)
.Range("O4,G11:G12").Font.Bold = False
Else: .Range("O4,G11:G12").Interior.Color = RGB(255, 153, 0)
.Range("O4,G11:G12").Font.Bold = True
End If
End Select
you could use Precedents property of Range obejct:
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
Select Case Target.Address
Case "$O$4"
Cancel = True
Dim rng As Range
Set rng = Target.Precedents
If rng Is Nothing Then
Set rng = Target
Else
Set rng = Union(Target, rng, rng.Offset(-1))
End If
If rng.Interior.Color = RGB(255, 153, 0) Then
rng.Interior.Color = RGB(255, 255, 255)
rng.Font.Bold = False
Else
rng.Interior.Color = RGB(255, 153, 0)
rng.Font.Bold = True
End If
End Select
End Sub
Try this approach, please:
Dim strForm As String, newRng As Range
Select Case Target.Address
Case "$O$4" '<~ if cell A1 is clicked, highlight cells C5-C9 yellow
Cancel = True
strForm = Target.Formula
'split string formula on "$" character and use the array obtained elements, to build the new address
Set newRng = Range(Range(Split(strForm, "$")(1) & Split(strForm, "$")(2) - 1).Address & ":" & _
Range(Split(strForm, "$")(1) & Split(strForm, "$")(2)).Address)
If Range(Target.Address(0, 0) & "," & newRng.Address).Interior.Color = RGB(255, 153, 0) Then
Range(Target.Address(0, 0) & "," & newRng.Address).Interior.Color = RGB(255, 255, 255)
Range(Target.Address(0, 0) & "," & newRng.Address).Font.Bold = False
Else: Range(Target.Address(0, 0) & "," & newRng.Address).Interior.Color = RGB(255, 153, 0)
Range(Target.Address(0, 0) & "," & newRng.Address).Font.Bold = True
End If
End Select
Thank you very much for both of you. Finaly it works. I have an additonal qiestion in order to reduce the length of the code.
Lets consider it as a block for coloring cells if I clcik on [O4] cell. If I dont want to repeate this block many times but I want the same feature for the range of [O4:O45] with corresponding refernce content how I can do that? Sorry I'm not so experenced in coding but I'm very satisfied wth the result so far.
Thanks a lot!
I have an Excel form whose borders are in black color. I would like to change it to other color. I tried the following code:
ActiveSheet.UsedRange.Borders.Color = RGB(255, 0, 0)
It changed the borders of all cells, including those cells which did not have borders, into red. This is not what I want. I want those borders in black to turn red and the invisible borders to stay invisible. Is there a way to do it?
Just another way of doing things making use of FindFormat and ReplaceFormat properties.
Sub BordersReplace()
With ThisWorkbook.Sheets(1)
For X = xlEdgeLeft To xlEdgeRight
With Application.FindFormat.Borders(X)
.Color = 0
End With
With Application.ReplaceFormat.Borders(X)
.Color = 255
End With
.Cells.Replace What:="", Replacement:="", searchformat:=True, ReplaceFormat:=True
Application.FindFormat.Clear
Application.ReplaceFormat.Clear
Next X
End With
End Sub
Small loop involved to go through the appropriate XLBordersIndex enumeration.
Note, not clearing FindFormat and ReplaceFormat will make Excel keep working with the first used format, hence why the .Clear is nesseccary.
I myself am a little bit puzzled on why it would't work on the cells with all edges on its borders applied. For that to work use Application.FindFormat.Borders()
Thanks for Mikku's input, I got the following code to work.
Sub change_border_color()
'change the color of existing borders
Dim cell As Range
Application.ScreenUpdating = False
For Each cell In ActiveSheet.UsedRange
If cell.Borders(xlEdgeLeft).LineStyle = 1 Then
cell.Borders(xlEdgeLeft).Color = RGB(0, 0, 255)
End If
If cell.Borders(xlEdgeTop).LineStyle = 1 Then
cell.Borders(xlEdgeTop).Color = RGB(0, 0, 255)
End If
If cell.Borders(xlEdgeBottom).LineStyle = 1 Then
cell.Borders(xlEdgeBottom).Color = RGB(0, 0, 255)
End If
If cell.Borders(xlEdgeRight).LineStyle = 1 Then
cell.Borders(xlEdgeRight).Color = RGB(0, 0, 255)
End If
Next
Application.ScreenUpdating = True
End Sub
Sub chgBorderColor_On_AllSheets()
'change the color of existing borders on all sheets
Dim Current As Worksheet
Dim cell As Range
Dim Red As Integer, Green As Integer, Blue As Integer
Dim NewColor As Long
Dim i As Integer
Red = Application.InputBox("Input R component of RGB", "Line color definition", Type:=1)
Green = Application.InputBox("Input G component of RGB", "Line color definition", Type:=1)
Blue = Application.InputBox("Input B component of RGB", "Line color definition", Type:=1)
NewColor = RGB(Red, Green, Blue)
Application.ScreenUpdating = False
For Each Current In Worksheets
For Each cell In Current.UsedRange
For i = xlEdgeLeft To xlEdgeRight '7 to 10
If cell.Borders(i).LineStyle = xlContinuous Or _
cell.Borders(i).LineStyle = xlDouble Or _
cell.Borders(i).LineStyle = xlDot Or _
cell.Borders(i).LineStyle = xlDash Or _
cell.Borders(i).LineStyle = xlDashDot Or _
cell.Borders(i).LineStyle = xlDashDotDot Or _
cell.Borders(i).LineStyle = xlSlantDashDot Then
cell.Borders(i).Color = NewColor
End If
Next
Next
Next
Application.ScreenUpdating = True
End Sub
Use this:
A Loop will work fine. Currently you are setting the complete Range and changing it's border, you only need to do that with cells having any Value.
This loop will colour the Border Red if cell currently have any border.
For Each cel In ActiveSheet.UsedRange
If Not cel.Borders(xlEdgeLeft).LineStyle = 0 Then
cel.Borders.Color = RGB(255, 0, 0)
End If
Next
This loop will color the Borders where the cel have some Value.
For Each cel In ActiveSheet.UsedRange
If Not cel.Value = "" Then
cel.Borders.Color = RGB(255, 0, 0)
End If
Next
I have a macro that does the following:
SETUP:
Compares a ID# between the "April Count" and "Prg-Srv Data" and turns the ones that are in common to a green cell background.
Filters the common data (anything with a green cell background) and copies that to a new worksheet "Medicaid Report". Then clears the AutoFilter and and formats the worksheet to specified style.
Filters and removes any rows that contain the word "Duplicate".
Finally it compares the April Count to the Medicaid Report to see if anyone has been missed from the April Count list.
PROBLEM IS THIS:
When the macro is finished it is still "randomly" marking data in the April Count that is also in the Medicaid Report and I'm not sure what I have done wrong.
Also if there is a more efficient way to do this let me know, this macro takes a long time to run and I'm not sure if its just because it has to do 5,000+ records or if I coded inefficiently. Thanks
CODE:
Sub ComparePrgSrv()
'Get the last row
Dim Report As Worksheet
Dim Report2 As Worksheet
Dim Report3 As Worksheet
Dim i, j, k As Integer
Dim LastRow, LastRow2, LastRow3 As Integer
Dim UniqueVal As New Collection
Dim Val As String
Set Report = Excel.Worksheets("April Count")
Set Report2 = Excel.Worksheets("Prg-Srv Data")
Set Report3 = Excel.Worksheets("Medicaid Report")
LastRow = Report.UsedRange.Rows.count
LastRow2 = Report2.UsedRange.Rows.count
LastRow3 = Report3.UsedRange.Rows.count
Application.ScreenUpdating = False
'April Count to Program Services comparison.
For i = 2 To LastRow2
For j = 2 To LastRow
If Report2.Cells(i, 1).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
If InStr(1, Report.Cells(j, 1).Value, Report2.Cells(i, 1).Value, vbTextCompare) > 0 Then
Report2.Cells(i, 1).Interior.Color = RGB(0, 102, 51) 'Dark green background
Report2.Cells(i, 1).Font.Color = RGB(0, 204, 102) 'Light green font color
Exit For
Else
Report2.Cells(i, 1).Interior.Color = xlNone 'Transparent background
Report2.Cells(i, 1).Font.Color = RGB(0, 0, 0) 'Black font color
End If
End If
Next j
Next i
'Filter Program Services to show correct data.
Report2.Range("$A$1:$M$" & LastRow2).AutoFilter Field:=1, Criteria1:=RGB(0, 102, 51), Operator:=xlFilterCellColor
'Copy filtered data to new worksheet.
Report2.Range("$A$1:$M$" & LastRow2).SpecialCells(xlCellTypeVisible).Copy Destination:=Worksheets("Medicaid Report").Range("A1")
'Clear filter selection on both sheets.
Report.AutoFilterMode = False
Report2.AutoFilterMode = False
'Format cell colors on Medicaid sheet.
Report3.UsedRange.Interior.Color = xlNone 'Transparent background
Report3.UsedRange.Font.Color = RGB(0, 0, 0) 'Black font color
Report3.Range("$A$1:$M$1").Interior.Color = RGB(31, 73, 125) 'Blue background
Report3.Range("$A$1:$M$1").Font.Color = RGB(255, 255, 255) 'White font color
'Filter and Delete Rows Containing "DUPLICATE"
With ActiveSheet
.AutoFilterMode = False
With Range("B1", Range("B" & Rows.count).End(xlUp))
.AutoFilter 1, "*DUPLICATE*"
On Error Resume Next
.Offset(1).SpecialCells(12).EntireRow.Delete
End With
.AutoFilterMode = False
End With
'April Count to Medicaid Report comparison.
For i = 2 To LastRow
For j = 2 To LastRow3
If Report.Cells(i, 1).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
If InStr(1, Report3.Cells(j, 1).Value, Report.Cells(i, 1).Value, vbTextCompare) > 0 Then
Report.Cells(i, 1).Interior.Color = xlNone 'Transparent background
Report.Cells(i, 1).Font.Color = RGB(0, 0, 0) 'Black font color
Exit For
Else
Report.Cells(i, 1).Interior.Color = RGB(156, 0, 6) 'Dark red background
Report.Cells(i, 1).Font.Color = RGB(255, 199, 206) 'Light red font color
End If
End If
Next j
Next i
End Sub
Workbook Setup:
First off, what do you mean by
"When the macro is finished "
For the effectiveness part:
You should remove the If Report2.Cells(i, 1).Value <> "" Then as it is already taken in account with the InStr. If cell is empty InStr will evaluate as 0; that should speed up a bit.
Secondly, you should get the last row of data using this:
LastRow = Report.Range("a" & Report.Rows.Count).End(xlUp).Row
LastRow2 = Report2.Range("a" & Report2.Rows.Count).End(xlUp).Row
LastRow3 = Report3.Range("a" & Report3.Rows.Count).End(xlUp).Row
"a" being the column containing the data to be checked. This will give you exactly the last non-empty row of the aimed column instead of the total used range of the entire sheet.
Also, in VBA, when you declare variables on one line, this:
Dim i, j, k As Integer
will only declare "k" as an Integer but "i" and "j" will be Variant
You should write it as:
Dim i As Integer, j As Integer, k As Integer. Same remark for Dim LastRow, LastRow2, LastRow3 As Integer
And don't forget to enable the Application.ScreenUpdating before exiting the Sub.
I want to compare 2 columns from 2 different worksheets (The same column in each sheet), then compare if they have increased or decreased, then color the cell red or green to indicate if increased or decreased. However my formula doesn't work...
I tried the columns on the same sheet using activecell, and activecell offset and it worked, but I cant seem to reference it in another sheet?
But is my (unworking) code...
Sub test3()
Range("A1").Select
Do
If ActiveWorkbook.worksheets(“Sheet1!”).Cell(ActiveCell) > ActiveWorkbook.worksheets(“Sheet2!”).Cell(“A1”) Then
ActiveCell.Interior.Color = RGB(255, 0, 0)
ElseIf ActiveWorkbook.worksheets(“Sheet1!”).Cell(ActiveCell) < ActiveWorkbook.worksheets(“Sheet2!”).Cell(“A1”) Then
ActiveCell.Interior.Color = RGB(0, 255, 0)
Else
End If
ActiveCell.Offset(1, 0).Select
Loop Until IsEmpty(ActiveCell)
End Sub
Try this, just need to set column to whatever column index you need.
Dim row As Integer
Dim column As Integer
row = 1
column = 1
ActiveWorkbook.Worksheets("Sheet1!").Cells.Interior.Color = RGB(255, 255, 255)
Do While ActiveWorkbook.Worksheets("Sheet1!").Cells(row, column) <> ""
If ActiveWorkbook.Worksheets("Sheet1!").Cells(row, column) <> ActiveWorkbook.Worksheets("Sheet2!").Cells(row, column) Then
If ActiveWorkbook.Worksheets("Sheet1!").Cells(row, column) > ActiveWorkbook.Worksheets("Sheet2!").Cells(row, column) Then
ActiveWorkbook.Worksheets("Sheet1!").Cells(row, column).Interior.Color = RGB(255, 0, 0)
Else
ActiveWorkbook.Worksheets("Sheet1!").Cells(row, column).Interior.Color = RGB(0, 255, 0)
End If
End If
row = row + 1
Loop
This will work - just change the colorRng reference to suit your needs:
Sub ColorCode()
Dim cl As Range, colorRng As Range
Set colorRng = Worksheets(1).Range("A1:A10")
For Each cl In colorRng
If cl > Worksheets(2).Cells(cl.Row, cl.Column) Then
cl.Interior.Color = RGB(255, 0, 0)
Else
cl.Interior.Color = RGB(0, 255, 0)
End If
Next cl
End Sub
I wanted to color highlight cells that are different from each other; in this case colA and colB. This function works for what I need, but looks repetitive, ugly, and inefficient. I'm not well versed in VBA coding; Is there a more elegant way of writing this function?
EDIT
What I'm trying to get this function to do is:
1. highlight cells in ColA that are different or not in ColB
2. highlight cells in ColB that are different or not in ColA
Sub compare_cols()
Dim myRng As Range
Dim lastCell As Long
'Get the last row
Dim lastRow As Integer
lastRow = ActiveSheet.UsedRange.Rows.Count
'Debug.Print "Last Row is " & lastRow
Dim c As Range
Dim d As Range
Application.ScreenUpdating = False
For Each c In Worksheets("Sheet1").Range("A2:A" & lastRow).Cells
For Each d In Worksheets("Sheet1").Range("B2:B" & lastRow).Cells
c.Interior.Color = vbRed
If (InStr(1, d, c, 1) > 0) Then
c.Interior.Color = vbWhite
Exit For
End If
Next
Next
For Each c In Worksheets("Sheet1").Range("B2:B" & lastRow).Cells
For Each d In Worksheets("Sheet1").Range("A2:A" & lastRow).Cells
c.Interior.Color = vbRed
If (InStr(1, d, c, 1) > 0) Then
c.Interior.Color = vbWhite
Exit For
End If
Next
Next
Application.ScreenUpdating = True
End Sub
Ah yeah that's cake I do it all day long. Actually your code looks pretty much like the way I'd do it. Although, I opt to use looping through integers as opposed to using the "For Each" method. The only potential problems I can see with your code is that ActiveSheet may not always be "Sheet1", and also InStr has been known to give some issues regarding the vbTextCompare parameter. Using the given code, I would change it to the following:
Sub compare_cols()
'Get the last row
Dim Report As Worksheet
Dim i As Integer, j As Integer
Dim lastRow As Integer
Set Report = Excel.Worksheets("Sheet1") 'You could also use Excel.ActiveSheet _
if you always want this to run on the current sheet.
lastRow = Report.UsedRange.Rows.Count
Application.ScreenUpdating = False
For i = 2 To lastRow
For j = 2 To lastRow
If Report.Cells(i, 1).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
If InStr(1, Report.Cells(j, 2).Value, Report.Cells(i, 1).Value, vbTextCompare) > 0 Then
'You may notice in the above instr statement, I have used vbTextCompare instead of its numerical value, _
I find this much more reliable.
Report.Cells(i, 1).Interior.Color = RGB(255, 255, 255) 'White background
Report.Cells(i, 1).Font.Color = RGB(0, 0, 0) 'Black font color
Exit For
Else
Report.Cells(i, 1).Interior.Color = RGB(156, 0, 6) 'Dark red background
Report.Cells(i, 1).Font.Color = RGB(255, 199, 206) 'Light red font color
End If
End If
Next j
Next i
'Now I use the same code for the second column, and just switch the column numbers.
For i = 2 To lastRow
For j = 2 To lastRow
If Report.Cells(i, 2).Value <> "" Then
If InStr(1, Report.Cells(j, 1).Value, Report.Cells(i, 2).Value, vbTextCompare) > 0 Then
Report.Cells(i, 2).Interior.Color = RGB(255, 255, 255) 'White background
Report.Cells(i, 2).Font.Color = RGB(0, 0, 0) 'Black font color
Exit For
Else
Report.Cells(i, 2).Interior.Color = RGB(156, 0, 6) 'Dark red background
Report.Cells(i, 2).Font.Color = RGB(255, 199, 206) 'Light red font color
End If
End If
Next j
Next i
Application.ScreenUpdating = True
End Sub
Things I did differently:
I used my integer method described above (as opposed to the 'for each' method).
I defined the worksheet as an object variable.
I used vbTextCompare instead of its numerical value in the InStr function.
I added an if statement to omit blank cells. Tip: Even if only one
column in the sheet is extra long (e.g., cell D5000 was accidentally
formatted), then the usedrange for all columns is considered 5000.
I used rgb codes for the colors (it's just easier for me since I
have a cheat sheet pinned to the wall next to me in this cubicle
haha).
Well that about sums it up. Good luck with your project!
'Compare the two columns and highlight the difference
Sub CompareandHighlight()
Dim n As Integer
Dim valE As Double
Dim valI As Double
Dim i As Integer
n = Worksheets("Indices").Range("E:E").Cells.SpecialCells(xlCellTypeConstants).Count
Application.ScreenUpdating = False
For i = 2 To n
valE = Worksheets("Indices").Range("E" & i).Value
valI = Worksheets("Indices").Range("I" & i).Value
If valE = valI Then
Else:
Worksheets("Indices").Range("E" & i).Font.Color = RGB(255, 0, 0)
End If
Next i
End Sub
' I hope this helps you