Conditional Formatting - how to reference a cell itself? - vba

I was trying to write VBA code to set conditional formatting for a column, so that when the cell contains mix of lower-case and upper-case letter, fill the cell with yellow color. Column number will be determined by variable c. And last row number will be determined by variable last_row.
The following code didn't work as desired. It looks like LCase(xlCellValue) always returns "1" instead of the cell content in lower case format. Thus the condition becomes "CellValue <> 1", which didn't do what I wanted it to do.
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlCellValue, xlNotEqual, LCase(xlCellValue)). _
Interior.ColorIndex = 6
So I guess I'll have to use xlExpression instead of xlCellValue. But how shall I write the formula so it'll take variables c and last_row? Thank you!

Here, try this:
Sub test()
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlExpression, , "=NOT(Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Lower(" & Cells(1, c).Address(RowAbsolute:=False) & ")))"). _
Interior.ColorIndex = 6
End Sub
I don't know if the comparison with the xlCellValue, xlNotEqual would be case-sensitive; however, Excel has a function Exact which is a case-sensitive comparison. I added a Not to cause the return from the formula to be true when the string is not all lower case. Since this is set-up using Excel Functions, I also replaced Lcase with Lower.
Note:
Using the logic you were following, if the entire string is upper-case, it also gets colored yellow. If you only want the conditioning when it's a mix of cases, you should use the below code instead:
Sub test()
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlExpression, , "=NOT(OR(Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Lower(" & Cells(1, c).Address(RowAbsolute:=False) & ")),Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Upper(" & Cells(1, c).Address(RowAbsolute:=False) & "))))"). _
Interior.ColorIndex = 6
End Sub

You're adding the format condition to [ActiveSheet.]Range([ActiveSheet.]Cells(1, c), [ActiveSheet.]Cells(last_row, c)) - you can extract that into a local variable:
Dim target As Range
Set target = ActiveSheet.Range(ActiveSheet.Cells(1, c), ActiveSheet.Cells(last_row, c))
And then use that variable to refer to the cell:
Dim fCondition As FormatCondition
Set fCondition = target.FormatConditions.Add(xlCellValue, xlNotEqual, LCase$(target.Value))
It looks like LCase(xlCellValue) always returns "1" instead of the cell content in lower case format.
That's because xlCellValue is a constant, with a value of 1. LCase(xlCellValue) is therefore equivalent to LCase(1), which will always only ever return "1".

Related

copy cell content based if adjacent cell meets criteria

I have a series of matrices consisting of 7 columns with a varied number of rows. I want the company names that are in column 2 of the matrix if the corresponding data in column 4 is "CM" aggregated into one cell per matrix (lets say B3:B98 for all the different matrices) with a space in between the different names. Please see the below picture for an example of what the matrices look like
The end result is that all the company names in Column E will be aggregated in B3 if the cell on the same row in column G is "CM", the next matrix beginning in Column M in B4 and so on.
I am having zero success in getting my if statement to recognize "CM" in the cell content, or aggregating the results using the Join statement. Any help is much appreciated.
Edits:
The objective is to have all the underwriters on a particular security aggregated in one cell, so that the cell can be easily searched in another part of the sheet for the presence of a particular underwriter.
The code below, as you can likely tell, does not work. I hit a wall as I could not get it to distinguish between cells that contained "CM" and those that did not. (I know that the code below would not aggregate the result in any cell, only copying the result into column B, as I said, it is a work in progress that has stalled.)
Dim Ws5 As Worksheet: Set Ws5 = Worksheets(5)
'turn off some Excel functionality so code runs faster
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
'Compiles the managers in the matrices into a column on the MgrMatrix sheet to be used
'for the entry sheet column of underwriters.
Dim CoL As Range: Set CoL = Ws5.Range("D3:K104")
Dim CeL As Range
For Each CeL In CoL.Columns(4)
If CeL.Text = "CM" Then
CeL.Offset(0, -5) = "CM"
Else
CeL.Offset(0, -5) = CeL.Offset(0, -2).Value
End If
Next
Edit: Using urdearboy's code, i modified it to work for multiple matrices on the same sheet in the below way. This version doesn't have the same finesse as his did, as this version relies on all matrices containing the same number of columns and not exceeding 100 rows.
For i = 7 To 857 Step 9
For y = 3 To 100
If Cells(y, i) = "CM" Then
s = s & Cells(y, i).Offset(0, -1).Value & " "
End If
Next y
If s = "" Then
s = "Sole Lead"
End If
Cells(Rows.Count, 2).End(xlUp).Offset(1, 0) = Trim(s)
s = ""
Next i
Paste code in VBE within Sheet 5 (Or whatever sheet you want to run this on).
The string, s, will build itself as it loops through your column checking for "CM" matches.
As is, the code will add commas between each new value added like, so, and, so, and then remove the last coma at the end before displaying the final string like, so, and, so
Option Explicit
Sub TextCM()
Dim i As Long, s As String
For i = 3 To Range("G" & Rows.Count).End(xlUp).Row
If Range("G" & i) = "CM" Then
s = s & Range("E" & i).Value & ", " 'Remove & ", " if you do not want the comma + space
End If
Next i
Range("B2") = Left(s, Len(s) - 2) 'Change to Range("B2") = s to not delete last character in string
End Sub
You should be able to figure out how to extend this to multiple tables (matrices?) no problem.

VBA: match of a variable range

I have a problem. For a certain spreadsheet I want to find out the position (only column) of a value smaller than threshold (further called maxt). I have to solve this in VBA as I need them on a different worksheet to give out accumulated numbers.
I am able to retrieve the max smaller than threshold but the vba match function gives back an error that the number couldnt be found.
However, if the value maxt is copied to a cell and I use the the normal match function on the sheet with the cell containing maxt as condition (=MATCH(cell of maxt; range), it works without any issues.
Problem (I only have A to C filled in my example; irrelevant as it doesnt work on only a few constellations).
A B C
8 5 6 -> doesn't work (Error: 1004)
5 6 7 -> works
7 6 7 -> works
4 8 5 -> works
Below is the code.
Dim myVar As Double
Dim myVarAdress As Long
For I = 1 To 10
myVar = Evaluate("=MAX(IF(A" & I & ":M" & I & "<6, A" & I & ":M" & I & "))")
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
Next I
End Sub
Thanks in advance
Change myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
to myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I), 0)
That "0" means that you are looking for the exact match. Also you should add a conditon that will skip the 0 value of your "myVar" variable. For example:
If myVar > 0 Then
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & i & ":M" & i), 0)
End If
Your setup has a couple of different problems that must be addressed before a true solution can be found.
1) You are trying to evaluate a MAX() function, that only has one argument. Your IF() function will either return a value less than 6, or FALSE (0). So either your MAX() function is irrelevant, your IF() function is irrelevant, or you left out one or more arguments on either/both of those functions. In any case, there is no set behavior for what to do when there is no item less than 6 in a row. This raises the possibility that myVar is 0, which is likely to cause faulty results because:
2) You left off the third argument in the MATCH() function. Because your range is (currently) larger than your data set, when you leave off the third argument for MATCH() it will fail any time the data is not organized correctly. This is particularly problematic when you are returning FALSE from your IF() function (treated as 0 by MAX()) because MATCH() is matching to the blanks in your data. Which means that the size of your data set does matter. If you had all 13 rows filled in, your first line would (probably) not fail, but it would not actually match to the 5 you want it to if there were any values LOWER than 5 to the right of column B. Also, it potentially causes some of the other lines to fail if suddenly there aren't any values below 6 in any of the columns and there are no blanks for MATCH() to find and treat as a 0.
All that being said, without further clarification of how you want to clean up these problems, here is a proposed solution (that assumes you want the first occurrence of your max value less than 6, regardless of how many occurrences there are):
Sub MatchSub()
Dim myVar As Double
Dim myVarAdress As Long
Dim rngMaxT As Range
Dim wsFindMax As Worksheet
Set wsFindMax = ActiveSheet
For i = 1 To 10
myVar = Evaluate("=IF(A" & i & ":M" & i & "<6, A" & i & ":M" & i & ")")
Set rngMaxT = wsFindMax.UsedRange.Rows(i)
If rngMaxT(1, 1).Value = myVar Then
myvaraddress = 1
Else
Set rngMaxT = rngMaxT.Find(myVar, , xlValues, xlWhole, xlByRows, xlNext, False)
If rngMaxT Is Nothing Then
'There is no value in the row less than 6
Else
myVarAdress = rngMaxT.Column
End If
End If
Next i
End Sub

VBA EXCEL Range syntax

I don't understand syntax for range.
Why does this work:
For i = 1 To 10
Range("A" & i & ":D" & i).Copy
Next
But this doesn't work:
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A" & lastRow), 0)
Next
Why do I need to use
For i = 2 To lastRow
'num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
Next
What A1:A mean? Why can't I use
Range("A" & lastRow), 0
There is nothing wrong with your syntax and your code should've work just fine.
The problem with using worksheet function like Match, Vlookup and other look up functions is that if the value being searched is not found, it throws up an error.
In your case, you are trying to search multiple values in just one cell.
So let us say your lastrow is 9. You're code will loop from Cell(2,1) to Cell(9,1) checking if it is within Range("A" & lastrow) or Range("A9").
If your values from Cell(2,1) through Cell(9,1) is the same as your value in Range("A9"), you won't get an error.
Now, if you use Range("A1:A" & lastrow), it will surely work cause you are trying to match every element of that said range to itself and surely a match will be found.
WorksheetFunction.Match(Cells(2,1), Range("A1:A9")) 'will return 2
WorksheetFunction.Match(Cells(3,1), Range("A1:A9")) 'will return 3
'
'
'And so on if all elements are unique
It doesn't matter if you use Range("A9") or Range("A1:A9").
What matters is that you handle the error in case you did not find a match.
One way is to use On Error Resume Next and On Error Goto 0 like this:
Sub ject()
Dim num As Variant
Dim i As Long, lastrow As Long: lastrow = 9
For i = 2 To lastrow
On Error Resume Next
num = WorksheetFunction.Match(Cells(i, 1), Range("A" & lastrow), 0)
If Err.Number <> 0 Then num = "Not Found"
On Error GoTo 0
Debug.Print num
Next
End Sub
Another way is to use Application.Match over WorksheetFunction.Match like this:
Sub ject()
Dim num As Variant
Dim i As Long, lastrow As Long: lastrow = 9
For i = 2 To lastrow
num = Application.Match(Cells(i, 1), Range("A" & lastrow), 0)
Debug.Print num
'If Not IsError(num) Then Debug.Print num Else Debug.Print "Not Found"
Next
End Sub
Application.Match works the same way but it doesn't error out when it returns #N/A. So you can assign it's value in a Variant variable and use it later in the code without any problem. Better yet, use IsError test to check if a value is not found as seen above in the commented lines.
In both cases above, I used a Variant type num variable.
Main reason is for it to handle any other value if in case no match is found.
As for the Range Syntax, don't be confused, it is fairly simple.
Refer to below examples.
Single Cell - All refer to A1
Cells(1,1) ' Using Cell property where you indicate row and column
Cells(1) ' Using cell property but using just the cell index
Range("A1") ' Omits the optional [Cell2] argument
Don't be confused with using cell index. It is like you are numbering all cells from left to right, top to bottom.
Cells(16385) ' refer to A2
Range of contiguous cell - All refer to A1:A10
Range("A1:A10") ' Classic
Range("A1", "A10") ' or below
Range(Cells(1, 1), Cells(10, 1))
Above uses the same syntax Range(Cell1,[Cell2]) wherein the first one, omits the optional argument [Cell2]. And because of that, below also works:
Range("A1:A5","A6:A10")
Range("A1", "A8:A10")
Range("A1:A2", "A10")
Non-Contiguous cells - All refer to A1, A3, A5, A7, A9
Range("A1,A3,A5,A7,A9") ' Classic
Without any specific details about the error, I assume that Match does not return the value you expect, but rather an #N/A error. Match has the syntax
=match(lookup_value, lookup_range, match_type)
The lookup_range typically consists of a range of several cells, either a column with several rows or a row with several columns.
In your formula, you have only one cell in the lookup_range. Let's say Lastrow is 10. The first three runs of the loop produce the formula
=Match(A2,A10,0)
=Match(A3,A10,0)
=Match(A4,A10,0)
It is a valid formula but in most cases the result won't be a match but an error. Whereas what you probably want is
=Match(A2,A1:A10,0)
Looking again at your code, stitch it together and find why you need A1:A as a string constant in your formula:
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
Next

VBA find cell of closest value

I have an excel file that looks like:
12123 51212
12123.5 45832
12124 37656
12124.5 32987
12125 42445
and so on, where column A is always 0.5 increasing and column B has a certain output.
Now I have a specific value in cell E2, say 12124,23 and I want a VBA code to return, in this case, that the best matching value is in cell A3, because I need this cell location in further code, I don't need the corresponding value in column B. I don't know how to start, however. The file can be up to 30000 rows big.
I'd only like to know first which method to use, then I will try to write the code myself of course :)
JV
You don't have to use VBA for your problem, Excel will do it perfectly fine!
Try this
=vlookup(E2;A:A;2;true)
and for what you are trying to do, you HAVE TO sort your A column in an ascending fashion, or else you will get an error!
And if you do need that in VBA,
a simple for+if structure with a test like this
Function pr24(ByVal Value_To_Match As Double) As Range
For i = 2 To ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
If Cells(i, 1) > Value_To_Match Then
If Abs(Cells(i - 1, 1) - Value_To_Match) >= Abs(Cells(i, 1) - Value_To_Match) Then
pr24 = Range(Cells(i, 1))
Else
pr24 = Range(Cells(i - 1, 1))
End If
Exit For
Else
End If
Next i
End Function
or you can use the worksheet function Vlookup
Application.WorksheetFunction.VLOOKUP()
You could use VLOOKUP function for this:-
Application.WorksheetFunction.VLOOKUP(lookup_value, table_array, column_index, range_lookup)
Set your values as below:-
lookup_value = 12124.23
table_array = would be the range Ax:Bx containing your values
column_index = 2 (the second column of table_array)
range_lookup = true
Setting range_lookup to true means that if the vlookup doesn't find the exact value it will return the closest match.
Note this will only work if the values in column A are sorted in ascending order.
Hope this helps.
You need to sort your data in column A first (smallest to largest), and then you can use a simple lookup formula:
=LOOKUP(E2,A:A)
If you don't want to sort the data, then you can use a VBA loop like so - however this is very inefficient - you should always use worksheet formulas where you can:
Sub SO()
Dim resultCell As Excel.Range
Dim checkCell As Double
Dim bestDiff As Double
checkCell = Range("E2").Value
bestDiff = checkCell
For i = 1 To Range("A" & Rows.count).End(xlUp).Row
If Range("A" & i).Value <= checkCell Then
If (checkCell - Range("A" & i).Value) < bestDiff Then
bestDiff = checkCell - Range("A" & i)
Set resultCell = Range("A" & i)
End If
End If
Next i
MsgBox "Best match is in " & resultCell.Address
Set resultCell = Nothing
End Sub
You dont'need VBA, a call co VLOOKUP Excel function will do the trick; remember to set the last parameter to true, to find a non exact match with the searched value
It should be like something similar to:
= VLOOKUP(E2, A:B, 2, true)

Am I using the isnumeric function correctly?

This program is to convert a column of data from cumulative to non-cumulative. On my sheet I have A1, B1, and C1 with the text Non-Cumulative, Cumulative, and Converted, respectively. I have numbers 1 to 10 beneath A1, then them summed cumulatively beneath B1. C1 is where I want to convert column B back to non-cumulative.
The IsNumeric is used to make the first row of data in C equal to the first row of data in B. It should detect that the title is above the number it is evaluating, thus knowing that no calculations have to be performed. For the rest of them, it'll see that the number above the one it is evaluating is a number, and thus the calculation has to be done.
My problem is that it isn't working. I think the reason is because IsNumeric() keeps coming back as false. Is there a different function I should be using? Do cell references not work in IsNumeric?
Here's the program!
Option Explicit
Dim i As Variant
Sub Conversion()
Sheets("Test Sheet").Select
For i = 1 To 10
If IsNumeric("B" & i) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next
End Sub
The way you wrote your code is logical, just a minor syntax changes you need initially. However,
It's also best to check if the range is empty first...
Then check on if the value is numeric.
Better even, if you set the Range into a Range object and use offset
Code:
Option Explicit '-- great that you use explicit declaration :)
Sub Conversion()
Dim i As Integer '-- integer is good enough
Dim rngRange as Range
'-- try not to select anything. And for a cleaner code
Set rngRange = Sheets("Test Sheet").Range("B1")
For i = 1 To 10
If (rangeRange.Offset(i,0).value) <> "" then '-- check for non-empty
If IsNumeric(rangeRange.Offset(i,0).value) = False Then
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0)
Else
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0) - rangeRange.Offset(i-1,0)
End If
End if
Next i '-- loop
End Sub
To make your code more dynamic:
Another suggestion, you may simply Application.WorkSheetFunction.Transpose() the entire B column range that you need to validate into a variant array
Process the array and Transpose back to the Range with column B and C.
By doing so, you may omit setting for loop size manually but setting it using Lower and Upper bound of the array ;)
You need to check if the range of B i is numeric, not the string "B" & i
and rather than selecting the sheet, simply using a parent identifier like:
sheets("sheet1").range("B" & i)
This will help you avoid errors in your code
For i = 1 To 10
If IsNumeric(sheets("test sheet").range("B" & i).value) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next