Check colour of entire column - excel

I'm trying to create an if statement that checks the colour of column B.
It works if I target a single cell in column B not when I try ("B:B").
This is what I have.
Sub FOO()
Dim answer As Range
Set answer = Range("b:b")
If answer.Interior.Color = vbRed Then
MsgBox ("There is an issue with column B, please review.")
End If
End Sub

As we are getting many quality answers, here is the most optimized code. Fastest, I bet :)
It wont work if you are using one of the ancient versions of excel. anything 2007+ is fine.
Sub OptimizedFOO()
Dim rngTemp
With Application.FindFormat.Interior
.Color = vbRed
End With
'/ Sheet1 is example sheet name
Set rngTemp = Sheet1.Columns(2).Find(What:="", SearchFormat:=True)
If Not rngTemp Is Nothing Then
MsgBox ("There is an issue with column B, please review.")
End If
End Sub
Old answer
Sub FOO()
Dim answer As Range
Dim cell As Range
'/ This will show message if at least one cell is found with red color
Set answer = Range("b:b")
For Each cell In answer.Cells
If answer.Interior.Color = vbRed Then
MsgBox ("There is an issue with column B, please review.")
Exit For
End If
Next
End Sub

I am not sure, but give you my best guess.
If VB unifies the properties, then it unifies the properties of all the cells of the column. You can then compare the property to a value and this will be True if all rhe proeprties have that (same) value. Otherwise the comparison will be False.
So If answer.Interior.Color = vbRed will be True if all cells have this propety value vbRed. If you want to check if any of the cells have that color, you may need to iterate over all the cells.
I believe VB and the VB object model work like this, but again, I am not sure.

I would find last used row on column B and than loop through them.
Sub FOO()
Dim LR As Long, I As Long
LR = findLastRow("Sheet1", "B")
For I = 1 To LR
If Range("B" & I).Interior.Color = vbRed Then
MsgBox ("There is an issue with column B, please review.")
Exit For
End If
Next I
End Sub
Function findLastRow(shtName As String, colLetter As String) As Long
With Sheets(shtName)
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
findLastRow = .Cells.Find(What:="*", _
After:=.Range(colLetter & "1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
findLastRow = 1
End If
End With
End Function

You could do something using the autofilter, something like this
Function AnyRedCells(rngRangeToInspect As Excel.Range) As Boolean
Application.ScreenUpdating = False
rngRangeToInspect.AutoFilter
rngRangeToInspect.AutoFilter field:=1, Criteria1:=RGB(255, 0, 0), Operator:=xlFilterCellColor
' Using >1 as assuming header on column
AnyRedCells = (ActiveSheet.AutoFilter.Range.SpecialCells(xlCellTypeVisible).Cells.Count > 1)
rngRangeToInspect.AutoFilter
Application.ScreenUpdating = True
End Function
Used like so
Sub OptimizedFOO2()
If AnyRedCells(Range("b23:b26")) Then
MsgBox ("There is an issue with column B, please review.")
End If
End Sub

Related

Reliably get Last Column in Excel with or without Merged Cells

I recently ran into an issue where my get_lcol function returned A1 as the cells in A1:D1 were merged. I adapted my function to account for this, but then I had some other data with cells merged in A1:D1 but another column in G and my function returned D1 so I adjusted it again. The problem is I don't trust it still to work with all data types as its only checking merged cells in row 1.
Take a look at the below data, how can I reliably get the function to return D or 4 regardless of where I move the merged row and/or any other issues I haven't foreseen?
Current Function:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
Dim sEmpty As Boolean
On Error Resume Next
sEmpty = IsWorksheetEmpty(Worksheets(WS.Name))
If sEmpty = False Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
If IsMerged(Cells(1, Get_lCol)) = True Then
If Get_lCol < Cells(1, Get_lCol).MergeArea.Columns.Count Then
Get_lCol = Cells(1, Get_lCol).MergeArea.Columns.Count
End If
End If
Else
Get_lCol = 1
End If
End Function
Update:
Try this data w/ function:
This is a twist on the classic "Find Last Cell" problem
To state the aim:
find the column number of the right most cell containing data
consider merged cell areas that extend beyond other cells containing data. Return the right most column of a merged area should that extend beyond other data.
exclude formatted but empty cells and merged areas
The approach:
Use Range.Find to locate the last data cell
If the last column of the Used Range = Found last data cell column, return that
Else, loop from the last column of the Used Range back to the found data cell column
test for data in that column (.Count > 0), if true return that
test for merged cells in that column (IsNull(.MergeCells))
if found, loop to find the merged area
test the left most cell of the merged area for data
if found return the search column
Note
this may still be vulnerable to other "Last data" issues, eg Autofilter, Hidden rows/columns etc. I haven't tested those cases.
Has the advantage of limiting the search for merged cells to the relavent right most columns
Function MyLastCol(ws As Worksheet) As Long
Dim ur As Range
Dim lastcell As Range
Dim col As Long
Dim urCol As Range
Dim urCell As Range
Set ur = ws.UsedRange
Set lastcell = ws.Cells.Find("*", ws.Cells(1, 1), xlFormulas, , xlByColumns, xlPrevious)
For col = ur.Columns.Count To lastcell.Column - ur.Column + 2 Step -1
Set urCol = ur.Columns(col)
If Application.CountA(urCol) > 0 Then
MyLastCol = urCol.Column
Exit Function
End If
If IsNull(urCol.MergeCells) Then
For Each urCell In urCol.Cells
If urCell.MergeCells Then
If Not IsEmpty(urCell.MergeArea.Cells(1, 1)) Then
MyLastCol = urCol.Column
Exit Function
End If
End If
Next
End If
Next
MyLastCol = lastcell.Column
End Function
#Toddleson got me on the right track, here is what I ended with:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
On Error Resume Next
If Not IsWorksheetEmpty(WS) Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim Cell As Range
For Each Cell In WS.UsedRange
If Cell.MergeCells Then
With Cell.MergeArea
If .Cells(.Cells.Count).Column > Get_lCol Then Get_lCol = .Cells(.Cells.Count).Column
End With
End If
Next Cell
Else
Get_lCol = 1
End If
End Function
The Find Method Backed Up by the UsedRange Property: What?
Talking about wasting time...
Option Explicit
Function GetLastColumn( _
ByVal ws As Worksheet) _
As Long
If ws Is Nothing Then Exit Function
' Using the 'Find' method:
'If ws.AutoFilterMode Then ws.AutoFilterMode = False ' (total paranoia)
Dim lcCell As Range
Set lcCell = ws.Cells.Find("*", , xlFormulas, , xlByColumns, xlPrevious)
If Not lcCell Is Nothing Then
GetLastColumn = lcCell.Column
End If
Debug.Print "After 'Find': " & GetLastColumn
' Using the 'UsedRange' property (paranoia):
Dim rg As Range: Set rg = ws.UsedRange
Dim clColumn As Long: clColumn = rg.Columns.Count + rg.Column - 1
If clColumn > GetLastColumn Then
If rg.Address(0, 0) = "A1" Then
If IsEmpty(rg) Then
Exit Function
End If
End If
GetLastColumn = clColumn
'Else ' clColumn is not gt GetLastColumn
End If
Debug.Print "Final (if not 0): " & GetLastColumn
End Function
Sub GetLastColumnTEST()
Debug.Print "Sub Result: " & GetLastColumn(Sheet1)
Debug.Print Sheet1.UsedRange.Address(0, 0)
End Sub
' It works for a few (?) cells, otherwise it returns 'Null'.
Sub TestMergeCells() ' Useless?! Could someone confirm.
Debug.Print Sheet1.Cells.MergeCells ' Null for sure
Debug.Print Sheet1.UsedRange.MergeCells
End Sub

How can I select the only filled/colored cell in a column?

I am currently working with a workbook that utilizes a row of cells that are colored/filled grey to separate between two sets of data. There isn't any real structure to the way the worksheet it set up, but if I can extract the data to a new sheet then I can format it with some other code I have. The first step for extracting the data would be for me to get to the second data set, which I can do if I can select the row of colored/filled cells. I attempted to use the record function and have come up with the following code:
Application.FindFormat.Clear
Columns("A:A").Select
With Application.FindFormat.Interior
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorDark1
.TintAndShade = -0.14996795556505
.PatternTintAndShade = 0
End With
Selection.Find(What:="", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=True).Activate
ActiveCell.Select
End Sub
The problem is that the above code activates/selects a blank cell with no fill. If anyone could provide me with some insight as to why that might be the case, I would really appreciate that.
Alternatively, if anyone has an idea as to how I can search a specific column for the only filled/colored cell and then return/select that cell address, that would be equally helpful.
Thanks in advance for any advice!
I tend to do things using my own approach rather than the in built way, I find it's then easier for me to debug. That's not true in all cases but with this approach, it is.
Throw this function into a new module in VBA ...
Public Function GetColoredCells(ByVal rngCells As Range) As Range
Dim objCell As Range, strCells As String
For Each objCell In rngCells.Cells
If objCell.Interior.ColorIndex <> xlColorIndexNone Then
strCells = strCells & "," & Replace(objCell.Address, "$", "")
End If
Next
strCells = Trim(Mid(strCells, 2))
Set GetColoredCells = rngCells.Range(strCells)
End Function
Public Sub YourRoutineToCopyAndPaste()
Dim rngCells As Range
Set rngCells = GetColoredCells(Sheet1.Range("A1:G13"))
' From here, you can work with the individual cells that the
' GetColoredCells function returned.
rngCells.Select
End Sub
It doesn't necessarily give you the exact outcome to what you require but you can do something with the logic and it demonstrates the approach. I trust you can take it to the next step.
This is how I'd do it, just adjust rng and then add code for MsgBox
Public Sub FindFilled()
Dim rng As Range
Dim rcell As Range
Set rng = Range("A1:A255")
For Each rcell In rng.Cells
If rcell.Interior.ColorIndex <> xlColorIndexNone Then
MsgBox "Shading in Cell" & rcell.Address ' Do Code Here
rcell.select
End If
Next rcell
End Sub

Use Find/Replace to clear vbNullString

I have a spreadsheet that is generated as a report in our Enterprise system and downloaded into an Excel spreadsheet. Blank cells in the resulting spreadsheet are not really blank, even though no data is present - and the blank cells do Not contain a 'space' character.
For example, the following cell formula in A2 returns TRUE (if A1 is a blank cell):
=IF(A1="","TRUE","FALSE")
However,
=ISBLANK(A1)
returns FALSE.
You can replicate this problem by typing an apostrophe (') in a cell and copying the cell. Then, use Paste Special...Values to paste to another cell and the apostrophe is not visible in the pasted cell, nor in the Formula Bar. There appears to be a clear cell, but it will evaluate to FALSE using ISBLANK.
This causes sorting to result in the fake blank cells at the top of an ascending sort, when they need to be at the bottom of the sort.
I can use a vba loop to fix the fake blanks, to loop through every column and evaluate
IF Cell.VALUE = "" Then
Cell.Clear
but because the spreadsheet has tens of thousands of rows of data and as many as 50 columns, this adds substantial overhead to the program and I would prefer to use FIND and Replace.
Here is the code that does not currently work:
Range("ZZ1").Copy
Range("Table1[#All]").Select
With Selection
.Replace What:="", Replacement:=.PasteSpecial(xlPasteValues, xlNone, False, False), _
LookAt:=xlWhole, SearchOrder:=xlByColumns, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
End With
The following things do not work to clear the fake blank cells either:
Replacement:= vbnullstring
Replacement:= ""
Replacement:= Cells.Clear
Replacement:= Cells.ClearContents
Replacement:= Cells.Value = ""
I have tried 20 other things that do not work either.
Try this
With ActiveSheet.UsedRange
.NumberFormat = "General"
.Value = .Value
End With
A variant array provides an efficient way of handling the false empties:
Sub CullEm()
Dim lngRow As Long
Dim lngCol As Long
Dim X
X = ActiveSheet.UsedRange.Value2
For lngRow = 1 To UBound(X, 1)
For lngCol = 1 To UBound(X, 2)
If Len(X(lngRow, lngCol)) = 0 Then X(lngRow, lngCol) = vbNullString
Next
Next
ActiveSheet.UsedRange.Value2 = X
End Sub
The problem is that you are searching for a hidden .PrefixCharacter which are not covered by the standard replacement function. For more information on this you might want to visit MSDN: https://msdn.microsoft.com/en-us/library/office/ff194949.aspx
In order to find and replace these you'll have to use the .Find function because it can look at the formulas (rather than only at a cell's value). Here is a short sample code to illustrate that:
Option Explicit
Public Sub tmpTest()
Dim cell As Range
Dim rngTest As Range
Dim strFirstAddress As String
Set rngTest = ThisWorkbook.Worksheets(1).Range("A1:G7")
Set cell = rngTest.Find("", LookIn:=xlFormulas, lookat:=xlPart)
If Not cell Is Nothing Then
strFirstAddress = cell.Address
Do
cell.Value = vbNullString
Set cell = rngTest.FindNext(cell)
Loop While strFirstAddress <> cell.Address And Not cell Is Nothing
End If
End Sub
I can't figure out anything that you could put in Replacement to get that to work. I'm afraid you're stuck looping. You can reduce the overhead by using .Find instead of looping through every cell.
Sub ClearBlanks()
Dim rng As Range
Dim rFound As Range
Dim sFirstAdd As String
Dim rFoundAll As Range
Set rng = Sheet1.UsedRange
Set rFound = rng.Find(vbNullString, , xlValues, xlWhole)
If Not rFound Is Nothing Then
sFirstAdd = rFound.Address
Do
If rFoundAll Is Nothing Then
Set rFoundAll = rFound
Else
Set rFoundAll = Application.Union(rFound, rFoundAll)
End If
Set rFound = rng.FindNext(rFound)
Loop Until rFound.Address = sFirstAdd
End If
If Not rFoundAll Is Nothing Then
rFoundAll.ClearContents
End If
End Sub
You can use the table filter to select the (seemingly) blank cells in each column and clear the contents. This should be quicker than finding each blank cell.
Sub clearBlankTableEntries()
Dim tbl As ListObject, c As Byte
Set tbl = ActiveSheet.ListObjects("testTable")
For c = 1 To tbl.Range.Columns.Count
tbl.Range.AutoFilter Field:=c, Criteria1:="="
Range(tbl.Name & "[Column" & c & "]").ClearContents
tbl.Range.AutoFilter Field:=c
Next c
End Sub

Clearing Cell Values of a table if matching criteria

I am looking to sort through a specific range of cells and look to see if the LEN(gth) of that cell is equal to 20. If it is, I would like to clear the contents of said cell and move to the next until they are all evaluated.
I have this report that is pulled from a website and exported to excel and the annoyance is a 0.0% that shows up as pound signs (# x's 20). I've tried other macros that search for strings, it completely ignores 0%, the one constant is that each cell that this appears in is exactly 20 characters in length.
What should happen is that I am:
1 Searching C3:U20
2 for cell.len = 20
3 if activecell = matches criteria
4 then clear.contents
5 Goto 1 until all activecells with a len of 20 are cleared
Thank you for any assistance you can provide.
G
*EDIT*
Since I couldn't make that work, I used a "work around". It's inefficient as all blarp but since it's only used on a small table it doesn't really matter. I just have to do this in "three" seperate scripts. I found that if I converted the ranges from Percentage formatting to General, I could just look for the overflow number as a string. Then once they are "cleared" I just reconvert those columns to a percentage formatting:
Sub sub_ConvertToGeneral()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''' Convert the Percentage Columns to General Format
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Columns("F:F").Select
'Application.FormulaBarHeight = 2
Range("F:F,H:H,L:M,O:O,S:S,U:U").Select
Range("U1").Activate
Selection.NumberFormat = "General"
'Selection.NumberFormat = "0.00%"
Range("A1:B1").Select
End Sub
Sub sub_Overflow()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''' Find Overflow and delete every instance of it
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim FindString As String
Dim rng As Range
FindString = "2.6965E+308"
50
If Trim(FindString) <> "" Then
With Sheets("Main Import").Range("C1:U20")
Set rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rng Is Nothing Then
Application.Goto rng, True
ActiveCell.ClearContents
Do
Set rng = .FindNext(rng)
GoTo 50
Loop While Not rng Is Nothing
Else
End If
End With
End If
Range("A1").Select
End Sub
Sub sub_ConvertToPercentage()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''' Convert the General Columns to Percentage
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Columns("F:F").Select
'Application.FormulaBarHeight = 2
Range("F:F,H:H,L:L,O:O,S:S,U:U").Select
Range("U1").Activate
'Selection.NumberFormat = "General"
Selection.NumberFormat = "0.00%"
Range("A1:B1").Select
End Sub
If the cells to be cleared actually contain exactly 20 characters (So LEN(A1) would display 20), then Select the cells you want to examine and try:
Sub dural()
For Each r In Intersect(Selection, ActiveSheet.UsedRange)
If Len(r.Text) = 20 Then
r.Clear
End If
Next r
End Sub
EDIT#1:
This version will clear cells with a value greater than 1.0E+307:
Sub dural()
For Each r In Intersect(Selection, ActiveSheet.UsedRange)
If IsNumeric(r.Value) Then
If r.Value > 1E+307 Then
r.Clear
End If
End If
Next r
End Sub

Default Value From Drop Down List

I wonder whether someone may be able to help me please.
I'm using the code below, which among a number of actions being performed, automatically populates column "A" with the date, and column "AS" with the text value "No" when a new record is created within a Excel spreadsheet.
Option Explicit
Public preValue As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Cell As Range, res As Variant
Dim rCell As Range
Dim Rng1 As Range
Dim Rng2 As Range
Dim Rng3 As Range
Application.EnableCancelKey = xlDisabled
'Sheets("Input").Protect "handsoff", UserInterFaceOnly:=True, AllowFiltering:=True, AllowFormattingColumns:=True
If Target.Column = 3 Then
If Target = "No" Then MsgBox "Please remember to make the same change to all rows for " & Target.Offset(0, -1).Value & " and delete any future forecasts"
End If
If Target.Cells.Count > 1 Then Exit Sub
On Error Resume Next
If Not Intersect(Target, Range("B5:AD400", "AF5:AQ400")) Is Nothing Then
If Target.Value <> preValue And Target.Value <> "" Then
Application.EnableEvents = False
With Rows(Target.Row)
.Range("A1").Value = Date
.Range("AS1").Value = "No"
End With
Application.EnableEvents = True
Target.Interior.ColorIndex = 35
End If
End If
On Error GoTo 0
If Target.Column = 45 Then
If Target.Value = "Yes" Then
Set Rng1 = Application.Union(Cells(Target.Row, "B").Resize(, 19), Cells(Target.Row, "R"))
Rng1.Interior.ColorIndex = xlNone
Set Rng2 = Application.Union(Cells(Target.Row, "S").Resize(, 12), Cells(Target.Row, "AD"))
Rng2.Interior.ColorIndex = 37
Set Rng3 = Application.Union(Cells(Target.Row, "AF").Resize(, 12), Cells(Target.Row, "AQ"))
Rng3.Interior.ColorIndex = 42
End If
End If
If Not Intersect(Target, Range("J7:J400")) Is Nothing Then
Set Cell = Worksheets("Lists").Range("B2:C23")
res = Application.VLookup(Target, Cell, 2, False)
If IsError(res) Then
Range("K" & Target.Row).Value = ""
Else
Range("K" & Target.Row).Value = res
End If
End If
End Sub
What I'd like to do, if at all possible, is when the date is inserted into column "A", I'd like to insert the text value "Select" on the same row in column "C". This value is taken from the first value I have in a drop down menu, set up on a sheet called "Lists" with the named range "RDStaff".
Could someone perhaps tell me please how I may go about changing the functionality, so that as soon as column "A" is populated with the date, the first value from my list i.e. "Select" is automatically populated in column "C"?
Many thanks and kind regards
Chris
It is not clear exactly which cell in column C is where your validation list is being used from, but if you add the code below into your with statement it should work, of course, adjusting to the appropriate drop-down cell.
.Range("C1").Value = Sheets(1).Range("C10").Value
Now, this assumes your drop down list, based on your validation is in the first sheet of your workbook (by index) in cell C10. You'll need to adjust these to match your data / workbook structure.
The point is that you don't hard code the value. You reference the value from the drop-down list location.
Per your comments, here is a code snippet to add the validation list into your code.
With Rows(Target.Row)
'... your existing code
With Range("C1").Validation
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Lists!RDStaff ' you may need to make this named range global for it to work on another sheet in this context
.IgnoreBlank = True
.InCellDropdown = True
End With
End WIth

Resources