Excel - lots of conditional formatting converted to VBA - vba

For an Excel spreadsheet order form, I need a way to apply conditional formatting to all rows with one macro, from row 78 down until there is no more data.
Each column has its own conditional formatting formulas. Some have multiple formulas. I tried using the Record Macro function, but since there is so much going on, the resulting VBA code is messy, and I'm not sure how to combine it all.
I don't need someone to write all the code for me, but I'm hoping I can get a little guidance to figure out how to do all of it.
There are about 15 columns that need conditional formatting applied to them. Here are a few columns to show what I'm working with:
A78:
Formula: =AND($A$78="",COUNTA(78:78)>=1) | white text, red fill | Stop
If True
C78:
Format only cells that contain > Specific Text > beginning with > M |
no format | Stop If True
Format only cells that contain > Specific Text > beginning with > F |
no format | Stop If True
Format only cells that contain > No Errors | red background, white
text
D78:
Cell value is greater than 300

You can do this fairly easily with a DO-WHILE Loop. I'll give a start for "D78" and you should be able to finish the rest.
sub formatCells()
Dim count as Integer
Range("D78").Activate
count = 0
Do While ActiveCell.Offset(count, 0).Value <> ""
If ActiveCell.Offset(count, 0).Value > 300 Then
'Do Stuff
End If
count = count + 1
Loop
End Sub

You need to create a FormatCondition object for each rule you want to set. Here are the basics for setting up a formula-based conditional format.
'Set a variable for the formatcondition to make it easier to work with.
Dim fc As FormatCondition
'Create the formatcondition.
strFormula = "=$A1=$B1"
Set fc = Range("A:A").FormatConditions.Add(Type:=xlExpression, Formula1:=strFormula) '(There is a "Formula2" property that only applies if you are using one of the built-in conditional formatting rule types. It does not apply if you are using an xlExpression rule type).
'Move it to the top of the list (optional).
fc.SetFirstPriority
'Set "Stop if True" (optional).
fc.StopIfTrue = True
'Set interior Color (optional).
fc.Interior.Color = RGB(255,0,0) 'red
'Set borders (optional).
arBorders = Array(xlLeft, xlRight, xlTop, xlBottom)
For Each borderConst In arBorders
fc.Borders(borderConst).LineStyle = xlContinuous
Next
'Set font (optional).
fc.Font.Italic = True
fc.Font.Bold = True
fc.Font.Underline = True
I would recommend setting up a procedure as below to simplify this process. I created this one for my own use. It can only set borders and fill color, but could be modified to set font attributes, etc.
Sub AddFormatCondition(rgAppliesTo, strFormula, Optional bSetFirstPriority, Optional FillColor, Optional bBorders, Optional bStopIfTrue)
Dim fc As FormatCondition
Set fc = rgAppliesTo.FormatConditions.Add(Type:=xlExpression, Formula1:=strFormula)
If Not IsMissing(bSetFirstPriority) Then
If bSetFirstPriority Then fc.SetFirstPriority
End If
If Not IsMissing(FillColor) Then
With fc.Interior
.Color = FillColor
End With
End If
If Not IsMissing(bBorders) Then
If bBorders <> 0 Then
arBorders = Array(xlLeft, xlRight, xlTop, xlBottom)
For Each borderConst In arBorders
fc.Borders(borderConst).LineStyle = xlContinuous
Next
End If
End If
fc.StopIfTrue = bStopIfTrue
End Sub

Related

Highlight matching strings, found in in a form-field text box, either from a table or static array (using MS Access)

I have an Access tool/database with external database (ODBC) connections. It's purpose is to review call logs for issues and the user will decide the severity based on the contents of a message.
I have an idea, to assist the review, using VBA. I created an with about 50 strings, and compare that to a field (memo format) in a form (bound to a table column). I want the routine to ONLY highlight the matching portion of the string.
An example is:
If the array string contains "Repor", it will change the font size and color of only those letters within the memo field Like Reported, . with be larger font and different color
I can successfully do this in Excel VBA with this section of code below ("findar" is a pre-built array, rng1 is the designated range)
For i = LBound(findar) To UBound(findar)
For Each rngcell In rng1
startPos = 0
startPos = InStr(rngcell, findar(i))
If InStr(rngcell, findar(i)) <> 0 Then
rngcell.Characters(startPos, Len(findar(i))).Font.Color = vbBlue
rngcell.Characters(startPos, Len(findar(i))).Font.Size = 18
End If
Next rngcell
Next I
"Character", apparently doesn't exist in Access, so I'm trying this, triggered in the "Got Focus" event: It fails with RunTime error 13. I'm certain this is doable, but apparently not by me.....
Dim i As Integer
Dim startpos As Long
'findar is an array
'incident text is inside the form field
findar = Array("returned", "failed") 'real array is about 50 strings
inctext = Me.txtincidentdesc
lngred = RGB(255, 0, 0)
lngblack = RGB(0, 0, 0)
'reset to default
Me.txtincidentdesc.FontBold = False
Me.txtincidentdesc.ForeColor = lngblack
Me.txtincidentdesc.FontSize = 10
startpos = 0
For i = LBound(findar) To UBound(findar)
With Me.txtincidentdesc
If InStr(inctext, findar(i)) <> 0 Then
SelStart = InStr(inctext, findar(i))
SelLength = Len(findar(i))
txtincidentdesc(Mid(inctext, SelStart, SelLength)).ForeColor = lngred 'fails here RunTime error 13
' Me.txtincidentdesc.ForeColor = lngred ' this works fine
' Me.txtincidentdesc.FontSize = 20 'this works fine
End If
End With
Next
End Sub
I've also considered using a recordset and compare that against the memo field but that also failed. Thanks for any input or help on this. Maybe I'm just approaching it wrong
Mark

Nothing Happening a Simple Condition in VBA

In excel, I want to impose a cell to be 0 if a condition is matched, but editable if not. My condition is that another cell's value = 1. This is my minimal example VBA line:
If Range("B4").Value = 1 Then Range("C4").Value = 0
But nothing is happening even though B4 is set to 1! Is there something missing in this code? Thanks!
Qualify the Range objects with the specific worksheets you want to analyze / alter. This is best practice for VBA and ensures the code acts on the specific places you need it to.
For example:
With Worksheets("Sheet1")
If .Range("B4").Value = 1 Then .Range("C4").Value = 0
End With
Or
If Worksheets("mySheet").Range("B4").Value = 1 Then
Worksheets("yourSheet").Range("C4").Value = 0
End If
You have to refer to the sheet as well. E.g.,
If ActiveSheet.Range("B4").Value = 1 Then ActiveSheet.Range("C4").Value = 0
To see the sheet, which you were referring to, try this:
Sub TestMe()
If Range("B4").Value = 1 Then Range("C4").Value = 0
MsgBox (Range("B4").Parent.Name)
End Sub

Can I use IsEmpty to refer to a different sheet and hide a column?

Is it possible to use IsEmpty to refer to a cell on a different sheet from where the macro is being fired from? Also, is it possible to hide the queried column if the result of that query is True?
Here's what I've built so far:
My first version looked like this:
If IsEmpty(L1) Then
Columns("L").EntireColumn.Hidden = True
Else
Columns("L").EntireColumn.Hidden = False
End If
Straightforward enough. But, that only works if it's fired from the worksheet where I want the query/hide to occur. When I launch the macro from the different sheet, it hides the column in that sheet (of course, duh).
So, after several iterations and errors, I got to this:
If IsEmpty(Sheets("Results").Cells(10, 1).Value) Then
Worksheets("Results").Columns(10).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(10).EntireColumn.Hidden = False
End If
Which at least doesn't throw any errors from the VBA. It also does a grand total of squat. :$ I'm starting to wonder if it's even possible to use IsEmpty on a different sheet? Or the EntireColumn.Hidden command? Also, given that I need to run this check on 9 columns, maybe there's a better way than 9 If/Then statements?
To get away from a loop through 9 columns' row 1, use SpecialCells(xlCellTypeBlanks).
dim blnks as range
with workSheets("Results")
with .range(.cells(1, "B"), .cells(1, "K"))
.entirecolumn.hidden = false
set blnks = .specialcells(xlCellTypeBlanks)
if not blnks is nothing then blnks.entirecolumn.hidden = true
end with
end with
Essentially this unhides all 9 columns then hides the columns with blank cells in the first row. Note that a zero-length string (e.g. "") returned by a formula is not the same thing as a truly blank cell.
I think you're very close, just you have the cells inputs the wrong way around:
If IsEmpty(Sheets("Results").Cells(1, 10).Value) Then
Worksheets("Results").Columns(10).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(10).EntireColumn.Hidden = False
End If
Additionally as mentioned in the comments you can create a loop to check many columns:
Dim i As Integer
Dim maxi As Integer
i = 1
maxi = 20
While i < maxi
If IsEmpty(ThisWorkbook.Worksheets("Results").Cells(1, i)) Then
Worksheets("Results").Columns(i).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(i).EntireColumn.Hidden = False
End If
i = i + 1
Wend

Color Rows Based on Repeated Values - excel vba

I'm trying to simplify an excel file I receive every week for my team's travel plans.
It has the team member's name, flight #, and arrival time.
I have team members come in on different flight sometimes. I like to visually see who will arrive at what time so I can easily make rental car arrangements. If a group arrives at 1:06, I'll highlight those together and another at 6:55 - I'll highlight those. We could have as many as 15 different flight plans. I currently use conditional formatting to identify the common ones, but since I do this every week for 50 people it would be convenient to through into a vba module to run. ( I already have a module reformatting some columns/rows).
I've looked at repeating code identifiers and this as my main resource
Compare Dates/Times but no luck so far.
Picture of what I do now:
highlight
The way I have coded is that the data is formatted into a table, then unique values in the 'Flight #' column are calculated. The data is then sequentially filtered by these values and the rows are coloured from a predetermined palette (which can be altered).
Sub FormatDuplicateRows()
Dim wsFlight As Worksheet: Set wsFlight = Worksheets("Flights")
On Error Resume Next
If Not wsFlight.ListObjects("Flights") Is Nothing Then wsFlight.ListObjects("Flights").Unlist
On Error GoTo 0
wsFlight.ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion, , xlYes).Name = "Flights"
Dim tblFlight As ListObject: Set tblFlight = wsFlight.ListObjects("Flights")
Dim Fld As Long: Fld = tblFlight.ListColumns("Flight #").Range.Column
Dim Dict As Object: Set Dict = CreateObject("Scripting.Dictionary")
For Each Cell In tblFlight.ListColumns("Flight #").DataBodyRange
If Not Dict.Exists(Cell.Value) Then Dict.Add Cell.Value, Cell.Address
Next
Dim Colours() As String: Colours = Split("&HD9E9FD,&HF3EEDB,&HECE0E5,&HDDF1EA,&HDCDDF2,&HCCFFFF", ",")
Dim i As Long: i = 0
With tblFlight
.TableStyle = "TableStyleLight1"
.ShowTableStyleRowStripes = False
For Each Value In Dict.Keys
.Range.AutoFilter Field:=Fld, Criteria1:=Value
.DataBodyRange.SpecialCells(xlCellTypeVisible).Interior.Color = Colours(i)
i = IIf(i = UBound(Colours), 0, i + 1)
Next Value
.Range.AutoFilter Field:=Fld
End With
End Sub
You can alter the palette to your requirements and the palette will auto-repeat once all the colours have been used once
********* UPDATE **********
To get the colour values for the array I coded the following function
Public Function GetColour(rngSrc As Range) As String
GetColour = "&H" & Application.WorksheetFunction.Dec2Hex(rngSrc.Interior.Color)
End Function
Then in an excel workbook, I just placed the formula =GetColour("A1") in "A2" and altered the fill colour of some cells along row 1 then drag-dropped the formula to get the hex values of the fill colours I wanted

How to extract specific text from a cell?

In this case, I want to extract the beginning text in a cell and leave the remainder intact.
e.g. a series of cells contain:
2nd Unit. Miami
3rd Production Staff. Toronto
1st Ad. San Francisco
I want to break this up without using Text to columns as previous rows are formatted differently and these last few rows are outliers that I want to handle.
I thought Regular Expressions might do it, but that seems a bit complex.
My algorithm idea is:
1. grab the wanted text (what function or custom sub would do that?)
2. Past the text to it's new location
3. Cut the text from the cell, leaving the remaining text.
Seems simple but I'm still wending my way through VBA forest, and at the rate I'm going it's going to end up faster doing it by hand. But this seems like a good opportunity to learn some VBA tricks.
TIA
Update:
I want to take the text up to the ".\ " and move it to a different column, keeping the remainder where it is.
VBA is unnecessary. To get the text after .\ in cell A1: =MID(A1,FIND(".\",A1,1)+2,LEN(A1)) to get the text before .\ in A1: =LEFT(A1,FIND(".\",A1,1)-1).
As additional information, Find returns the placement in the string where .\ appears. It is the equivalent of InStr in VBA. If .\ is not in the cell, it will display #VALUE, because I didn't bother to add error checking.
Since you seem to want to modify the cell text in place, VBA will be required.
Inside a loop that sets cl to the cell to be processed:
str = cl.value
i = Instr(str, ".\")
cl = Trim(Mid$(str, i + 2)) ' assuming you want to exclude the ".\"
cl.Offset(0, 1) Trim(Left$(str, i - 1)) ' Places the original first part one cell to the right
For the sake of anyone who had this same question, here is the fully tested, working code.
Function RE6(strData As String) As String
Dim RE As Object, REMatches As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = "[0-9][0-9][0-9][0-9]B"
RE6 = .test(strData)
End With
Set REMatches = RE.Execute(strData)
If REMatches.Count > 0 Then
RE6 = True
Else
RE6 = False
End If
End Function
Sub territory()
Dim strTest As String, str As String, cl As Range
strTest = ActiveCell.Value
Set cl = ActiveCell
If RE6(strTest) = True Then
str = cl.Value
i = InStr(str, ". ")
cl = Trim(Mid$(str, i + 2))
cl.Offset(0, 1) = Trim(Left(str, i - 1))
cl.Offset(0, 2) = "Instance"
MsgBox RE6(strTest)
End If
End Sub