VBA Countif Uppercase - excel

I'm trying to count the number of instances of a cell containing all uppercase characters in a user defined range, I've got some code already which loops through and highlights those uppercase cells correctly, but I'm struggling to apply that logic to VBA's Countif function. Here's the code I've got but its giving a mismatch error:
'count instances of all caps
Dim allcaps As Long
allcaps = Application.CountIf(Range(rngCompany.Cells(1, 1), rngCompany.Cells(Lastrow, 1)), UCase(Range(rngCompany.Cells(1, 1), rngCompany.Cells(Lastrow, 1))))
MsgBox "There are " & allcaps & " uppercase company names to review."
The code which is highlighting the cells correctly is:
'Highlight all caps company names for review
With ws
For i = 2 To Lastrow
' checks if cells in company name col are uppercase
If rngCompany.EntireColumn.Cells(i, 1).Value = UCase(rngCompany.EntireColumn.Cells(i, 1).Value) Then
wbk1.Sheets(1).Rows(i).Interior.ColorIndex = 6 '6: Yellow
Else
End If
Next i
End With
Is there a way to make the countif code work in a similar way within the loop? Thanks.

Here is how you can do it:
Function AllCapsCount(Target As Range) As Long
With Target.Parent
AllCapsCount = .Evaluate("=SUMPRODUCT(--EXACT(" & Target.Address & ",UPPER(" & Target.Address & ")))")
End With
End Function

Tim's suggestion of simply adding a counter within the loop was the simplest solution for me, after a long day I'd overlooked that way forward!
Code example for anyone coming across this in future:
AllCapsCount = 0
With ws
For i = 2 To Lastrow
' checks if cells in company name col are uppercase
If rngCompany.EntireColumn.Cells(i, 1).Value = UCase(rngCompany.EntireColumn.Cells(i, 1).Value) Then
wbk1.Sheets(1).Rows(i).Interior.ColorIndex = 6 '6: Yellow
AllCapsCount = AllCapsCount + 1
Else
End If
Next i
End With

Highlight and Count Cells if UCase but no LCase
Sub TESTgetAllCapsRange()
Dim rngCompany As Range
Set rngCompany = Range("A2:E11")
rngCompany.Interior.Color = xlNone
Dim rng As Range: Set rng = getAllCapsRange(rngCompany)
If Not rng Is Nothing Then
rng.Interior.Color = vbYellow
Dim AllCaps As Long: AllCaps = rng.Cells.CountLarge
If AllCaps > 1 Then
MsgBox "There are " & AllCaps _
& " uppercase company names to review."
Else
MsgBox "There is 1 uppercase company name to review."
End If
Else
MsgBox "There are no uppercase company names to review."
End If
End Sub
Function getAllCapsRange(rng As Range) As Range
If Not rng Is Nothing Then
Dim tRng As Range
Dim aRng As Range
Dim cel As Range
For Each aRng In rng.Areas
For Each cel In aRng.Cells
If Not IsError(cel) Then
If containsUCaseButNoLCase(cel.Value) Then
buildRange tRng, cel
End If
End If
Next cel
Next aRng
If Not tRng Is Nothing Then
Set getAllCapsRange = tRng
End If
End If
End Function
Function containsUCaseButNoLCase(ByVal CheckString As String) As Boolean
' Check if there is an upper case character.
If StrComp(CheckString, LCase(CheckString), vbBinaryCompare) <> 0 Then
' Check if there are no lower case characters.
If StrComp(CheckString, UCase(CheckString), vbBinaryCompare) = 0 Then
containsUCaseButNoLCase = True
End If
End If
End Function
Sub buildRange(ByRef BuiltRange As Range, AddRange As Range)
If Not AddRange Is Nothing Then
If Not BuiltRange Is Nothing Then
Set BuiltRange = Union(BuiltRange, AddRange)
Else
Set BuiltRange = AddRange
End If
End If
End Sub

Related

Conditionally Format Specific words within cells of worksheet

I have data such that single cells contain multiple bit of info.
Using conditional formatting excel can recognise cells which contain a phrase however the conditional format is applied to the cell as a whole. I'm aiming to try and Highlight the Specific text "Not Provided" in Red.
Looking online it looks like this will be a VBA solution.
To start, I have found the following code online however this doesnt appear to change the colour as desired.
Sub Test1()
Dim strString$, x&
Dim rngCell As Range
strString = Range("B1").Value
Application.ScreenUpdating = False
For Each rngCell In Range("G1", Range("G" & Rows.Count).End(xlUp))
With rngCell
.Font.ColorIndex = 1
For x = 1 To Len(.Text) - Len(strString) Step 1
If Mid(.Text, x, Len(strString)) = strString Then .Characters(x, Len(strString)).Font.ColorIndex = 5
Next x
End With
Next rngCell
Application.ScreenUpdating = True
End Sub
If someone could point out either how I can get the code above working to explore if this will be useful for my purposes or even if someone knows how to conditionally format specific words that would be great. Ultimately I wish for every instance of "Not Provided" across the worksheet to be highlighted in this way.
You can use InStr() to find substring instead of For loop
Sub Test1()
Dim strString As String, x As Long, rngCell As Range
strString = Range("B1").Value
Application.ScreenUpdating = False
For Each rngCell In Range("G1", Range("G" & Rows.Count).End(xlUp))
x = InStr(1, rngCell.Value2, strString, vbTextCompare)
If x > 0 Then
With rngCell
.Font.ColorIndex = 1
.Characters(x, Len(strString)).Font.ColorIndex = 3 'red color
End With
End If
Next
Application.ScreenUpdating = True
End Sub
To dynamically change the color of the text, as in conditional formatting, you can use the Change event:
'place it into the Worksheet module
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strString As String, x As Long, rngCell As Range, rng As Range
Set rng = Intersect(Target, Me.Columns("G"))
If rng Is Nothing Then Exit Sub
Application.EnableEvents = False
Application.ScreenUpdating = False
strString = Range("B1").Value
For Each rngCell In rng
x = InStr(1, rngCell.Value2, strString, vbTextCompare)
If x > 0 Then
With rngCell
.Font.ColorIndex = 1
.Characters(x, Len(strString)).Font.ColorIndex = 3 'red color
End With
End If
Next
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub

Auto-Updated Validated Cell When Source Value Changes

I'm trying to update cells that have data validation restrictions on them automatically.
For example - Sheet1 has below column (Column E):
Package Identifier
A
B
C
where the values are taken from the same named column (Column D) in Sheet2.
The below code works for MANUAL changes only
Sheet2 Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
Dim count_cells As Integer
Dim new_value As String
Dim old_value As String
Dim rng As Range
For count_cells = 1 To Range("D1").CurrentRegion.Rows.Count - 1
Set rng = Worksheets("Sheet1").Range("E3:E86")
If Intersect(Target, Range("D" & count_cells + 1)) Is Nothing Then
Else
Application.EnableEvents = False
new_value = Target.Value
Application.Undo
old_value = Target.Value
Target.Value = new_value
rng.Replace What:=old_value, Replacement:=new_value, LookAt:=xlWhole
Target.Select
End If
Next count_cells
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
So, if i manually change value B to Z, all the corresponding values that were B on Sheet1 now change to Z. The problem is, Package Identifier on Sheet2 is dictated by concatenating other columns
=CONCATENATE(B35, "-", "Package", "-", TEXT(C35, "#000"))
This piece of code breaks when trying to use it with the above formula. How can i make this set of code trigger on this formula based output?
Assuming this is how the Validation sheet looks
and this is how the Source sheet looks
Let's say user selects first option in Validation sheet.
Now go back to Source sheet and change 1 to 2 in cell C2.
Notice what happens in Validation sheet
If this is what you are trying then based on the file that you gave, test this code.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
Dim NewSearchValue As String
Dim OldSearchValue As String
Dim NewArrayBC As Variant
Dim OldArrayA As Variant, NewArrayA As Variant
Dim lRow As Long, PrevRow As Long
On Error GoTo Whoa
Application.EnableEvents = False
If Not Intersect(Target, Range("B:C")) Is Nothing Then
lRow = Range("A" & Rows.Count).End(xlUp).Row
'~~> Store new values from Col A, B and C in an array
NewArrayBC = Range("B1:C" & lRow).Value2
NewArrayA = Range("A1:A" & lRow).Value2
Application.Undo
'~~> Get the old values from Col A
OldArrayA = Range("A1:A" & lRow).Value2
'~~> Paste the new values in Col B/C
Range("B1").Resize(UBound(NewArrayBC), 2).Value = NewArrayBC
'~~> Loop through the cells
For Each aCell In Target.Cells
'~~> Check if the prev change didn't happen in same row
If PrevRow <> aCell.Row Then
PrevRow = aCell.Row
NewSearchValue = NewArrayA(aCell.Row, 1)
OldSearchValue = OldArrayA(aCell.Row, 1)
Worksheets("Validation").Columns(2).Replace What:=OldSearchValue, _
Replacement:=NewSearchValue, Lookat:=xlWhole
End If
Next aCell
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
A different approach from Sid's...
Instead of updating values in the DV cells when the source range changes, this replaces the selected value with a link to the matching cell in the DV source range.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngV As Range, rng As Range, c As Range, rngList As Range
Dim f As Range
On Error Resume Next
'any validation on this sheet?
Set rngV = Me.Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo 0
If rngV Is Nothing Then Exit Sub 'no DV cells...
Set rng = Application.Intersect(rngV, Target)
If rng Is Nothing Then Exit Sub 'no DV cells in Target
For Each c In rng.Cells
If c.Validation.Type = xlValidateList Then 'DV list?
Set rngList = Nothing
On Error Resume Next
'see if we can get a source range
Set rngList = Evaluate(c.Validation.Formula1)
On Error GoTo 0
If Not rngList Is Nothing Then
Application.EnableEvents = False
'find cell to link to
Set f = rngList.Find(c.Value, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
Application.EnableEvents = False
c.Formula = "='" & f.Parent.Name & "'!" & f.Address(0, 0)
Application.EnableEvents = True
End If
Else
Debug.Print "No source range for " & c.Address
End If
End If
Next c
End Sub

VBA program to color all cells that have a value

I just started teaching myself VBA so thanks in advance. Why is this giving me an error? The code searches for the column of dates that are in the future. Then searches in that column for any cells that have a value and colors them yellow.
Thanks!
Sub Macro1()
Dim cell As Range
Dim cell2 As Range
Dim ColumnN As Long
For Each cell In Range("I2:ZZ2")
If cell.Value > Now() Then
'
ColumnN = cell.Column
ColumnL = ConvertToLetter(ColumnN)
MsgBox ColumnL & cell.Row
For Each cell2 In Range("ColumnL:ColumnL")
If Not cell2 Is Empty Then
cell2.Interior.ColorIndex = 6
End If
Next cell2
End If
End Sub()
Function ConvertToLetter(lngCol As Long) As String
Dim vArr
vArr = Split(Cells(1, lngCol).Address(True, False), "$")
ConvertToLetter = vArr(0)
End Function
You were almost there!
There's two main problems to fix:
replace:
For Each cell2 In Range("ColumnL:ColumnL")
with
For Each cell2 In Range(ColumnL & ":" & ColumnL)
and
If Not cell2 Is Empty Then
with
If Not IsEmpty(cell2) Then
This should result in the following:
Sub Macro1()
Dim cell As Range
Dim cell2 As Range
Dim ColumnN As Long
Dim ColumnL As String
For Each cell In Range("I2:ZZ2")
If cell.Value > Now() Then
ColumnN = cell.Column
ColumnL = ConvertToLetter(ColumnN)
MsgBox ColumnL & cell.Row
For Each cell2 In Range(ColumnL & ":" & ColumnL)
If Not IsEmpty(cell2) Then
cell2.Interior.ColorIndex = 6
End If
Next cell2
End If
Next cell
End Sub
Function ConvertToLetter(lngCol As Long) As String
Dim vArr
vArr = Split(Cells(1, lngCol).Address(True, False), "$")
ConvertToLetter = vArr(0)
End Function
Although it is a little inefficient it gets the job done!
To check if a cell is empty, you need to switch the order of how that's done. Switch your If Not statement to If Not IsEmpty(cell2) Then.
Also, it is highly recommended not to name your variables cell, because this is a close to some "special words" (I forget the technical term) Excel uses. I always just use cel instead.
Sub test()
Dim cel As Range
Dim cel2 As Range
Dim ColumnN As Long
For Each cel In Range("I2:ZZ2")
If cel.Value > Now() Then
ColumnN = cel.Column
' ColumnL = ConvertToLetter(ColumnN)
' MsgBox ColumnL & cell.Row
If Not IsEmpty(cel) Then
cel.Interior.ColorIndex = 6
End If
End If
Next cel
End Sub
Edit: If you notice, I also tweaked your cell2 range. This removed the need to run another macro (which can be a cause of issues sometimes), so you only need the column Number.
Edit2: I removed the "ColumnL" range selection - what is that for? I can add it back in, but wasn't sure why you'd loop through I:ZZ columns, but only have the highlighting in column N.
Edit2:
I tweaked the code, now it's much shorter and should run a bit faster:
Sub Macro2()
Dim cel As Range, rng As Range
Dim lastCol As Long
Application.ScreenUpdating = False
lastCol = Cells(2, 9).End(xlToRight).Column ' Note, this assumes there are NO gaps in the columns from I:ZZ
'lastCol = cells(2,16384).End(xltoleft).column ' use this instead, if there are gaps in I2:ZZ2
Set rng = Range(Cells(2, 9), Cells(2, lastCol))
For Each cel In rng
If cel.Value > Now() Then
cel.Interior.ColorIndex = 6
End If
Next cel
Application.ScreenUpdating = True
End Sub

Create comments to a range of cells ftom the values of another range of cells

I want to create comments to a range of cells. The comments should contain the values of another range of cells.
Here is what I have so far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim sResult As String
If Union(Target, Range("A18")).Address = Target.Address Then
Application.EnableEvents = False
Application.ScreenUpdating = False
sResult = "Maximal " & Target.Value
With Range("I6")
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Application.EnableEvents = True
Application.ScreenUpdating = True
End If
End Sub
This works for one cell. I need this for a range of cells. For example, let's say I need the values of cells A1:F20 in comments of cells A21:F40. I do not want to copy the same Sub as many times.
It should do you the job if you replace
With Range("I6")
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
with
For Each cell In Range("A1", "F20").Cells
Dim V As Range
Set V = cell.Offset(20, 0)
With cell
.ClearComments
If Not IsEmpty(V) Then
.AddComment V.Value
End If
End With
Next
This will basically ignore all empty cells.
Output:
My code:
Sub TEST()
For Each cell In Range("A1", "F20").Cells
Dim V As Range
Set V = cell.Offset(20, 0)
With cell
.ClearComments
If Not IsEmpty(V) Then
.AddComment V.Value
End If
End With
Next
End Sub
I made some adaptions to your advices, thanks a lot, this solved my problem:
Private Sub Worksheet_Change(ByVal target As Range)
Dim src As Range: Set src = Worksheets("maxleft").Range("C2:K11")
Dim tar As Range: Set tar = Range("I6:Q15")
For i = 0 To tar.Rows.Count - 1
For j = 0 To tar.Columns.Count - 1
Dim sResult As String
sResult = "Maximal " & Worksheets("maxleft").Cells(src.Row + i, src.Column + j)
With Cells(tar.Row + i, tar.Column + j)
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Next j
Next i
End Sub
From your question I understand that you want to select a range of cells (e.g. "A1:A5"), then select another range of cells (e.g. "B6:B10") and the respective values of the first selected Range should be placed as comments in the secon selected Range. Is this correct?
The following code checks if 2 ranges with an equal length are selected and copies the values of the first selected range as comments to the second selected range:
Private Sub Worksheet_SelectionChange(ByVal target As Range)
If InStr(target.Address, ",") Then
Dim selected_range() As String
selected_range = Split(target.Address, ",")
If Range(selected_range(0)).Rows.Count = Range(selected_range(1)).Rows.Count Then
Dim src As Range: Set src = Range(selected_range(0))
Dim tar As Range: Set tar = Range(selected_range(1))
For i = 0 To src.Rows.Count - 1
Dim sResult As String
sResult = "Maximal " & Cells(src.Row + i, src.Column)
With Cells(tar.Row + i, tar.Column)
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Next i
End If
End If
End Sub

Location of cell and Extract numeric value

I'm currently "trying" to setup a grid in Excel where
the user inputs a reference (e.g. HP1 or HP234) and,
I can automatically detect the cell it was entered into and the numeric value in the cell (e.g. HP1 = 1, HP234 = 234).
I have started to play with the code below. In the section msgbox("work") - I'm using just to test code around it. Here I want to return the numeric value in the cell and the cell location so I can put them onto a report.
Any help would be greatly appreciated.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim rngTarget As Range
Set rngTarget = Range("a1:a100")
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
For Each rng In rngTarget
If InStr(1, prNg, "H") > 0 And InStr(1, rngEachValue, "P") = 0 Then
MsgBox ("works")
End If
Next
End If
End Sub
I found this a nice question so put some work into the answer. I think this will do just what you want! It even works with decimal and thousand separators.
I do admit the NumericalValue function could be created in a different way as well (find the first and the last number and take that mid part of the string.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim rngTarget As Range
Dim varValue As Variant
Set rngTarget = Range("a1:a100")
If Not Intersect(Target, rngTarget) Is Nothing Then
For Each rng In rngTarget
'only check cells that contain an H and a P
If InStr(1, rng, "H") > 0 And InStr(1, rng, "P") > 0 Then
'find the numerical value if any (Empty if not found)
varValue = NumericalValue(rng.Value2)
If Not IsEmpty(varValue) Then
MsgBox "hurray the value of cell " & rng.AddressLocal & " is " & varValue
End If
End If
Next
End If
End Sub
'return the first numerical value found in the cell
Private Function NumericalValue(ByVal strValue As String) As Variant
Dim intChar As Integer
Dim booNumberFound As Boolean
Dim intDecimal As Integer
booNumberFound = False
NumericalValue = Val(strValue)
For intChar = 1 To Len(strValue) Step 1
'if a number found then grow the total numerical value with it
If IsNumeric(Mid(strValue, intChar, 1)) Then
NumericalValue = NumericalValue * IIf(intDecimal = 0, 10, 1) + _
Val(Mid(strValue, intChar, 1)) * 10 ^ -intDecimal
If intDecimal > 0 Then
intDecimal = intDecimal + 1
End If
booNumberFound = True
'if the decimal separator is found then set the decimal switch
ElseIf intDecimal = 0 And booNumberFound = True And Mid(strValue, intChar, 1) = Application.DecimalSeparator Then
intDecimal = 1
'skip the thousand separator to find more numbers
ElseIf booNumberFound = True And Mid(strValue, intChar, 1) = Application.ThousandsSeparator Then
ElseIf booNumberFound = True Then
Exit For
End If
Next intChar
End Function
you're most of the way there, try the below:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim rngTarget As Range
Dim sText As String
Set rngTarget = Range("a1:a100")
If Not Intersect(Target, Range("A1:A100")) Is Nothing Then
For Each rng In rngTarget
If InStr(1, rng.Text, "H") > 0 And InStr(1, rng.Text, "P") > 0 Then
sText = rng.Text
sText = Replace(sText, "H", "")
sText = Replace(sText, "P", "")
Debug.Print rng.Address & " = " & Val(sText)
End If
Next
End If
End Sub

Resources