Override conditional formatting with Worksheet.BeforeDoubleClick event - excel

I am using this rule to format every other row depending on criteria in column B:
=IF(ISEVEN(LEFT($B1, FIND("-", $B1)-1)),MOD(ROW(),2))
I also want to be able to double-click a cell in a particular column to toggle highlighting that row with:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Intersect(Target, tb.ListColumns("Domain").DataBodyRange) Is Nothing Then
Cancel = True
Target.Name = "HighlightRow"
With ActiveCell
Range(Cells(.Row, .CurrentRegion.Column), Cells(.Row, .CurrentRegion.Columns.Count + .CurrentRegion.Column - 1)).Interior.ColorIndex = 36
End With
End If
End Sub
With reference to this answer, how can I override the rule so that the Worksheet.BeforeDoubleClick event takes precedence?
How can I adapt the Worksheet.BeforeDoubleClick event to toggle highlighting?

Before I start...I'm a little confused, is your conditional formatting formula supposed to be
=IF(ISEVEN(LEFT($B1, FIND("-", $B1)-1)),MOD(ROW(),2))
...which only formats Even numbers in column B on every Odd row?
leaving out all Odd numbers and all Even rows?
Anyway, you need to store the extra information of which cells are highlighted somewhere that the conditional formatting can use it.
Easy Way...
The easiest suggestion would be to add a hidden column called Highlight and refer to it in the conditional format.
Or Hard Way...
You could add a conditional format that takes priority and stops the others from being applied. I still used the Named Range idea from the other solution. I could have kept track of the highlighted range using a variable, but I think this works better. I also made it so that you could have multiple colors if you wanted (but I didn't add prioritization).
Enjoy...(I did)
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Intersect(Range("B:B"), Target) Is Nothing Then
ToggleHighlight Target.EntireRow, Range("A2:H50")
Cancel = True
End If
End Sub
Toggle Highlight
Sub ToggleHighlight(Target As Range, _
Optional TableArea As Range = Cells, _
Optional Name As String = "Yellow", _
Optional ColorIndex As Integer = 19)
Dim Formula As String
Dim HighlightedRows As Range
' Use unique names to allow multiple highlights/colors
' This is the formula we will apply to the highlighted area
Formula = "=OR(TRUE,""Highlight""=""" & Name & """)"
On Error Resume Next
' Check if the target cell that was clicked is within the table area
Set Target = Intersect(Target, TableArea)
If Target is Nothing Then Exit
' Get the current highlighted rows
Set HighlightedRows = ThisWorkbook.Names("HighlightedRows_" & Name).RefersToRange
ThisWorkbook.Names("HighlightedRows_" & Name).Delete
On Error GoTo 0
If HighlightedRows Is Nothing Then
Set HighlightedRows = Target ' We'll apply .EntireRow later
Else
' Remove previous Conditional Formats
Dim Condition As FormatCondition
For Each Condition In HighlightedRows.FormatConditions
With Condition
If .Formula1 = Formula Then .Delete
End With
Next
' Now, toggle the Target range/row
If Intersect(HighlightedRows, Target) Is Nothing Then
' We know that both HighlightedRows and Target are Not Nothing, so
Set HighlightedRows = Union(HighlightedRows, Target.EntireRow)
Else
' We're going to limit the (Big) area to a single column, because it's slow otherwise
Set HighlightedRows = InvertRange(Target.EntireRow, Intersect(HighlightedRows, TableArea.Columns(1)))
End If
End If
' Apply the new Conditional Formatting...
If Not HighlightedRows Is Nothing Then
' HighlightedRows is still set to the EntireRow
Set HighlightedRows = Intersect(HighlightedRows.EntireRow,TableArea)
With HighlightedRows
.Name = "HighlightedRows_" & Name
.FormatConditions.Add Type:=xlExpression, Formula1:=Formula
With .FormatConditions(.FormatConditions.Count)
' Make sure it's first
.SetFirstPriority
' and that no other format is applied
.StopIfTrue = True
.Interior.ColorIndex = ColorIndex
End With
End With
End If
End Sub
Invert Range
Function InvertRange(Target As Excel.Range, Optional LargeArea As Variant) As Excel.Range
' Returns the Inverse or Relative Complement of Target in LargeArea
' InvertRange = LargeArea - Target
Dim BigArea As Excel.Range
Dim Area As Excel.Range
Dim Cell As Excel.Range
If IsMissing(LargeArea) Then
Set BigArea = Target.Parent.UsedRange
Else
Set BigArea = LargeArea
End If
If Target Is Nothing Then
Set InvertRange = BigArea
ElseIf BigArea Is Nothing Then
' nothing to do; will return Nothing
Else
For Each Area In BigArea.Areas
For Each Cell In Area.Cells
If Intersect(Cell, Target) Is Nothing Then
If InvertRange Is Nothing Then
Set InvertRange = Cell
Else
Set InvertRange = Union(InvertRange, Cell)
End If
End If
Next Cell
Next Area
End If
End Function
Edit
I updated it to include TableArea, to limit the Highlights, as well as a Check that the Target and Table Area are on the same sheet and intersect.

Related

Automatically partial text Coloring in Excel(NOT CELLS)

I am newbie at excel and VBA so I came here to ask for your help.
I am looking for a VBA code to automatically color the first letter of each column cell.This is expected for column A only, not all columns in excel.
There are only words in the column.
For example, if the first letter is 'a' then the 'a' only will become red.There is no limit of case-sensitive and each of a-z will be colored in 26 distinguishable colors. I tried for a couple of days to find a solution, but unfortunately I couldn't.
Demonstration:
Thanks in advance.
There’s (at least) a couple of ways to do this. The first option below selects the color for you – you don’t get a choice, but the code is much shorter. The second option will require you to hard code the actual color you want for each letter – I’ve only gone as far as C for the sake of demonstration.
In both cases, you paste the code into the Sheet module area for the appropriate sheet. Let me know how it goes for you. I've taken this approach because you said you wanted it to occur "automatically"
Option One – shorter but no choice of color
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo GetOut
Application.EnableEvents = False
Dim myFont As Integer, aCell As Range
For Each aCell In Target.Cells
myFont = Asc(UCase(Left(aCell, 1))) - 62
aCell.Characters(Start:=1, Length:=1).Font.ColorIndex = myFont
Next
Continue:
Application.EnableEvents = True
Exit Sub
GetOut:
MsgBox Err.Description
Resume Continue
End Sub
Option Two – you choose the color you want, but must be added
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo GetOut
Application.EnableEvents = False
Dim myLetter As String, aCell As Range
For Each aCell In Target.Cells
myLetter = UCase(Left(aCell, 1))
Select Case myLetter
Case Is = "A"
aCell.Characters(Start:=1, Length:=1).Font.ColorIndex = 3 `<~~ change to your taste
Case Is = "B"
aCell.Characters(Start:=1, Length:=1).Font.ColorIndex = 4
Case Is = "C"
aCell.Characters(Start:=1, Length:=1).Font.ColorIndex = 5
'*** etc etc etc Add the rest of the alphabet***
End Select
Next
Continue:
Application.EnableEvents = True
Exit Sub
GetOut:
MsgBox Err.Description
Resume Continue
End Sub
To apply this to any (or certian) worksheets in the Workbook containing the code, place this code in the ThisWorkbook module
Option Explicit
Private Colours As Variant
' Define Colour Pallete
Private Sub PopulateColours()
ReDim Colours(0 To 25)
Colours(0) = vbRed 'A
Colours(1) = vbBlue 'B
' etc C..Z
End Sub
' Colour first character of each non-formula cell in range
Private Sub ColourCells(rng As Range)
Dim cl As Range
' if pallet not set, initialise it
If IsEmpty(Colours) Then PopulateColours
' loop the range
For Each cl In rng
' ignore formula, numeric and empty cells
If Not IsEmpty(cl) Then
If Not cl.HasFormula Then
If Not IsNumeric(cl.Value2) Then
If Not cl.Value2 = "" Then
With cl.Characters(1, 1)
.Font.Color = Colours(Asc(UCase(.Text)) - 65)
End With
End If
End If
End If
End If
Next
End Sub
' when and cell on any worksheet in the workbook changes...
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
' select specific sheets to apply colour to
Select Case Sh.Name
Case "Sheet1", "Sheet2"
' only colour column A
If Not Application.Intersect(Target, Sh.Columns(1)) Is Nothing Then
' call colouring routine
ColourCells Target.Columns(1)
End If
End Select
End Sub
If you want to apply this to any (or certain) open workbooks, you'll need an Application Events handler

Need help changing cell values when cell in same row changes

I need help automatically changing cells containing a certain value whenever a specific cell on same row changes value.
E.g whenever a cell in B column changes = change TRUE to FALSE on that specific row.
My VBA knowledge is pretty much nonexistent and Im certainly a beginner.
Im fairly sure that Worksheet.Change is what Im looking for and I've been trying out some code I've found here on SO, such as:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Target.Parent.Range("B:B")) Is Nothing Then Exit Sub
For Each x In Target
Cells(x.Row, 3).Value = "False"
Next
End Sub
I know though that this doesn't replace specific values in whatever column the cells are.
I've been trying out silly things like:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Target.Parent.Range("B:B")) Is Nothing Then Exit Sub
For Each x In Target
If Cells(x.Row, x.Column).Value = "TRUE" Then Value = "FALSE"
Next
End Sub
But of course it doesnt work.
Think you could point me out a direction of what I should be researching?
Replace the change event sub on the sheet where you have your data with the code below. I think that should do the trick
Private Sub Worksheet_Change(ByVal Target As Range)
Dim oRng As Range
Dim oCell As Range
' Check if change was in column B
If Intersect(Target, Target.Parent.Range("B:B")) Is Nothing Then Exit Sub
' Turn off events so that when we make a change on the sheet, this event is not triggered again
Application.EnableEvents = False
' Set the range to include all column in Target row
Set oRng = Target.Parent.Range("C" & Target.Row & ":" & Target.Parent.Cells(Target.Row, Target.Parent.UsedRange.Columns.Count).Address)
' Loop through all cells to change the value
For Each oCell In oRng
If Trim(LCase(oCell.Value)) = "true" Then
oCell.Value = "FALSE"
End If
Next
' Enable events again
Application.EnableEvents = True
End Sub

Find out if selected cell is in databodyrange

I have a macro set up to perform a chunk of code in an 'onclick' event, and I'm trying to determine if they've selected a numeric value in a table range. An example of my table is below, and I'm trying to figure out if they've selected a number > 0 in the second column. I know how to reference the second column of a specific table, something like:
ListObjects("Table1").ListColumns(2).DataBodyRange
but I'm not sure how to figure out if the selected cell is in that range. Any suggestions? Thanks so much for your help!
Use Intersect.
Dim rangeToCheck as Range
Set rangeToCheck = Intersect(ActiveCell, ListObjects("Table1").ListColumns(2).DataBodyRange)
If Not rangeToCheck Is Nothing Then
If IsNumeric(ActiveCell.Value) Then
If ActiveCell.Value > 0 Then
' do the suff
End If
End If
End If
Assuming your table has filters then you have to check the visible cells only
Try this code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Dim r As Range
Set r = ListObjects(1).ListColumns(2).DataBodyRange.SpecialCells(xlCellTypeVisible)
Dim hit As Boolean
hit = Not Application.Intersect(r, Target) Is Nothing
If hit Then
Range("A1").Value = "Inside"
Else
Range("A1").Value = "Outside"
End If
Application.EnableEvents = True
End Sub
otherwise for all visible and non-visible cells use
Set r = ListObjects(1).DataBodyRange

How can a format an entire row based on if a cell CONTAINS a specific word using vba?

I need to highlight and entire row if a cell string contains the word "risk". But I need to make it using vba since the person using it will write on it after using the macro.
I have something like:
The reason will be written afterwards and I need to highlight the row if someone writes the word risk anywhere in this column. Anything can be written there.
I use this to highlite a row when I want a full match:
lastReg= Cells(Rows.Count, 1).End(xlUp).Row
Set Rng = Range("A1:J" & lastReg)
Rng.FormatConditions.Add Type:=xlExpression, Formula1:="=$J1=""Plat"""
...
so I tried:
Rng.FormatConditions.Add Type:=xlExpression, Formula1:="=FIND(""risk"",$J1)>0"
But it doesn't work.
Edit: it gives me an execution error so the code itself doesn't run.
Edit2: Someone else uses this macro, and he can't do it by himself so I wanted the code to do it for him.
Also, the code is stored in the personal.xlsb because he runs the code in a different worksheet everyday, so I can't pre config the formatConditions for the worksheet.
I would use a worksheet change event. place this sub in your worksheet. Whenever any cell in column 5 changes and the value is "risk", it will color the row.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Rng As Range
Set Rng = Columns(5)
If Not Intersect(Rng, Target) Is Nothing And Target.Value = "risk" Then
Target.Offset(, -4).Resize(, 5).Interior.Color = vbYellow
End If
End Sub
Try:
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim cell As Range
If Not Intersect(Target, Sh.UsedRange) Is Nothing Then
For Each cell In Target.Cells
With cell
If UCase(.Value) = "RISK" Then
.Font.Color = vbRed
Else
.Font.Color = vbBlack
End If
End With
Next cell
End If
End Sub

Worksheet_Change Event - Duplication Check, Ignore Blanks

I am using a VBA change event to look for duplicates in column C. The code below works but when i delete all values within the range, blanks are triggered as duplicates so i need to include a way to ignore duplicates from the code. Any ideas?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
On Error GoTo ws_exit
Application.EnableEvents = False
With Target
If .Column = 3 Then
With .EntireColumn
Set cell = .Find(What:=Target.Value, AFter:=.Cells(1, 1))
If cell.Address = Target.Address Then
Set cell = .FindNext()
End If
If Not cell.Address = Target.Address Then
MsgBox "This Wall Reference already exists. Please ensure you have a unique reference identifier less than 20 characters in length", vbOKOnly
End If
End With
End If
End With
ws_exit:
Application.EnableEvents = True
End Sub
I expect to be able to ignore blanks but sill have the VBA run a duplication check to return a msgbox only if a duplication is found.
First you must consider that Target is a range of multiple cells and not only one cell. Therefore it is necessary to use Intersect to get all the cell that are changed in column 3 and then you need to loop through these cells to check each of them.
Also I recommend to use WorksheetFunction.CountIf to count how often this value occurs if it is >1 then it is a duplicate. This should be faster then using Find.
Note that the following code looks for duplicates in column 3 only if you want to check if a duplicate exists anywhere in the worksheet replace CountIf(Me.Columns(3), Cell.Value) with CountIf(Me.Cells, Cell.Value)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim AffectedRange As Range
Set AffectedRange = Intersect(Target, Me.Columns(3))
If Not AffectedRange Is Nothing Then
Dim Cell As Range
For Each Cell In AffectedRange
If Application.WorksheetFunction.CountIf(Me.Columns(3), Cell.Value) > 1 Then
MsgBox "This Wall Reference already exists. Please ensure you have a unique reference identifier less than 20 characters in length", vbOKOnly + vbExclamation
End If
Next Cell
End If
End Sub
Instead of using VBA you could also use Conditional Formatting to highlight duplicates in red for example. Could be easier to archieve (use the =CountIf formula as condition). And also it will always highlight all duplicates immediately which makes it easy to determine them.
Thanks for the help K.Davis. I appreciate your time and effort.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Value = vbNullString Then Exit Sub
Dim cell As Range
On Error GoTo ws_exit
Application.EnableEvents = False
With Target
If .Column = 3 Then
With .EntireColumn
Set cell = .Find(What:=Target.Value, AFter:=.Cells(1, 1))
If cell.Address = Target.Address Then
Set cell = .FindNext()
End If
If Not cell.Address = Target.Address Then
MsgBox "This Glazing Reference already exists. Please ensure you have a unique reference identifier less than 20 characters in length", vbOKOnly
End If
End With
End If
End With
ws_exit:
Application.EnableEvents = True
End Sub

Resources