VBA not able to discern between empty cells, numeric cells, and text? - vba

I am trying to read cells in VBA and do certain things based on the contents of them. However, VBA is unable to accurately determine if the contents of a cell are blank, numeric, or text. It will think that cells with only a '1' in them are blank, and cells with text are blank. If it makes a difference, I turn off all screen updating and the like when this runs.
Please see code below:
Function IsNumber(ByRef expression As Variant) As Boolean
IsNumber = Not IsEmpty(expression) And IsNumeric(expression)
End Function
Function IsText(ByRef expression As Variant) As Boolean
IsText = Not IsEmpty(expression) And Not IsNumeric(expression)
End Function
Sub RA()
Dim cell As Range
With ActiveWorkbook.Worksheets("Sheet1")
lastrow = .Cells(.Rows.Count, 1).End(xlUp).Row
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set r = Worksheets("Sheet1").Range(Cells(3, 3), Cells(lastrow, lastCol - 1))
End With
For i = 3 To lastrow
Set c = Cells(i, 3)
Select Case True
Case S1_Func(c)
End Select
Next i
End Sub
Function S1_Func(c As Variant)
Dim SR As Worksheet
Set SR = Worksheets("Financials")
Dim c2 As Range
Set c2 = c.Offset(0, 1)
If IsNumber(c2.Value2) Then
Select Case True
Case SR.Cells(i, 5).Value2 = "LY"
End Select
ElseIf IsText(c2.Value2) Then
Cells(i, 72).Value2 = "Incorrect"
End If
End sub

Updated after discussion below.
Function IsNumber(ByRef expression As Variant) As Boolean
IsNumber = expression <> "" And IsNumeric(expression)
End Function
Function IsText(ByRef expression As Variant) As Boolean
IsText = expression <> "" And Not IsNumeric(expression)
End Function
Original post:
There are some mixed problem in your code. The Select statement have several issues and is very misleading. In this case you can't use Select. If is your best option. Try something like this:
Sub Example()
If Len(Value) = 0 Then
'Empty
ElseIf IsNumeric(Value) Then
'Numeric
Else
'Alphanumeric
End If
End Sub

Related

Excel VBA: replace entire cell with value

Can you help me with loop that will go through cells A3:A50 and replace entire cell with a new value.
Reference below:
[ita-IT] to IT
[jpn] to JA
[por-BR] to PTBR
[spa-ES] to ES
etc.
Thanks for tips!
Try:
Sub ReplaceValues()
Dim r As Range
Dim v() As Variant
Dim i As Integer
v = [{"ita-IT","IT";"jpn","JA";"por-BR","PTBR";"spa-ES","ES"}]
Set r = ActiveSheet.Range("A3:A50")
For i = LBound(v) To UBound(v)
r.Replace what:=v(i, 1), replacement:=v(i, 2), lookat:=xlWhole, MatchCase:=False
Next i
End Sub
Edit:
There's no problem having 50 (or more) replacement pairs, but this would be much easier to manage by storing them in a table in the workbook, rather than listing them in a VBA array:
You can replace cell contents which have text before / after your lookup value by using wildcards. So combining those changes, your code now becomes:
Sub ReplaceValues2()
Dim r As Range
Dim v() As Variant
Dim i As Integer
v = Sheet1.ListObjects("tbReplacement").DataBodyRange
Set r = ActiveSheet.Range("A3:A50")
For i = LBound(v) To UBound(v)
r.Replace What:="*" & v(i, 1) & "*", Replacement:=v(i, 2), LookAt:=xlWhole, MatchCase:=False
Next i
End Sub
Here is an approach. If you get a lot of codes to replace it could look like spaghetti, and if there is a logic to the replacement, it would be nice to build in the logic, but it does what you asked, and is, I hope, readable.
Sub ReplaceStrings()
Dim result As String
For Each myCell In Range("A3:A50")
Select Case myCell.Value
Case "[ita-IT]"
result = "IT"
Case "[jpn]"
result = "JA"
Case "[por-BR]"
result = "PTBR"
Case "[spa-ES]"
result = "ES"
Case Else
result = myCell.Value
End Select
myCell.Value = result
Next myCell
End Sub
EDIT - To go with the original spirit but to meet the requirement of matching within the string, I replaced Case with an If Elseif series and used Like and wildcards to match. Note To match square brackets (assuming that was what you meant), I had to enclose them in square brackets. I also amended to reference the ActiveSheet to be safe, drawing on the other answer (which I am not criticizing, just showing another way to think about it).
Sub ReplaceStrings()
Dim result, s As String
For Each myCell In ActiveSheet.Range("A3:A50")
s = myCell.Value
If s Like ("*[[]ita-IT[]]*") Then
result = "IT"
ElseIf s Like "*[[]jpn[]]*" Then
result = "JA"
ElseIf s Like "*[[]por-BR[]]*" Then
result = "PTBR"
ElseIf s Like "*[[]spa-ES[]]*" Then
result = "ES"
Else
result = s
End If
myCell.Value = result
Next myCell
End Sub

Iterating through a range until you find different value in VBA

I'm trying to create a VBA function that starts from the bottom of a range, and returns the first value that's different from the value at the bottom.
Example:
In the above table, I'd like to be able to grab the last value in the "Month" column (11), and iterate to the top until the value 10 is reached, and return that value.
I just started looking into VBA 3 days ago and am very unfamiliar with the language so I'm still trying to grok the syntax.
I have no doubt that my thinking is fuzzy with this, so I'd really appreciate feedback on my errors.
Here's what I have right now:
Code:
Function NextValue(num1 As Range)
For c = num1.End(xlDown) To num1.Item(1)
If Cells(c, 1) <> num1.End(xlDown) Then
NextValue = Cells(c, 1)
Exit For
End If
Next c
End Function
In case it's not clear, here's a description of what I'm trying to do, line-by-line.
1). Initiate a For-Loop that begins at the end of a range and decrements to the top
2). Check if that cell does not match the last value in that column
3). If it does not, then set the value of the function to that value
4). Terminate If statements, For loops, and end the function.
Your help is greatly appreciated.
Try this:
Function NextValue(num1 As Range) as Integer
Dim y As Integer
'get the last cell from num1
Set num1 = num1.End(xlDown)
y = -1
Do Until num1.Offset(y, 0).Value <> num1.Value
y = y - 1
Loop
'set function return to the different cell
NextValue = num1.Offset(y, 0).value
End Function
This will handle both compact ranges and disjoint ranges:
Option Explicit
Public Function SomethingElse(rng As Range) As Variant
Dim r As Range, values() As Variant
Dim i As Long, strvalue As Variant
ReDim values(1 To rng.Count)
i = 1
For Each r In rng
values(i) = r.Value
i = i + 1
Next r
strvalue = values(rng.Count)
For i = rng.Count To 1 Step -1
If values(i) <> strvalue Then
SomethingElse = values(i)
Exit Function
End If
Next i
SomethingElse = CVErr(xlErrNA)
End Function
Not clear to me if you want an UDF or a code to be used in a macro
in the first case you've already been given answers
in the latter case you may want to consider these two options:
Public Function FirstDifferent(rng As Range) As Variant
With rng.Parent.UsedRange
With Intersect(.Resize(, 1).Offset(, .Columns.Count), rng.EntireRow)
.Value = rng.Value
.RemoveDuplicates Array(1)
FirstDifferent = .Cells(.Rows.Count, 1).End(xlUp).Offset(-1).Value
If FirstDifferent = .Cells(.Rows.Count, 1) Then FirstDifferent = "#N/A"
.ClearContents
End With
End With
End Function
Public Function FirstDifferent(rng As Range) As Variant
With rng.Resize(, 1)
.AutoFilter Field:=1, Criteria1:=.Cells(.Rows.Count, 1)
FirstDifferent = .Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Cells(1, 1).Offset(-1).Value ' = 0 '<-- if any rows filtered other than headers one then change their column "B" value to zero
If FirstDifferent = .Cells(.Rows.Count, 1) Then FirstDifferent = "#N/A"
.Parent.AutoFilterMode = False
End With
End Function

Not looping through array to match values

For some reason my code isn't working, I've used this type of code a thousand times and for whatever reason it's not matching.. When the column is blank however it does seem to match? Any suggestions on how I can change this or even improve this as I do realise 140,000 records is quite a lot!
Dim name1(140000) As String, name2(140000) As String, answer(140000) As String
For i = 1 To 140000
name1(i) = ActiveWorkbook.Worksheets("Sheet0").Cells(i, 1).value
name2(i) = ActiveWorkbook.Worksheets("Sheet1").Cells(i, 6).value
answer(i) = ActiveWorkbook.Worksheets("Sheet0").Cells(i, 13).value
If name1(i) = name2(i) Then
answer(i) = "yes"
End If
Next
Hi thanks for this, the problem being though the values are changing, so the name in sheet 1 might be in in "A1" but then in sheet 2 be in "F12" and then next week could be in "F14" so its just a way of using the code to update accordingly, also used your vba and still no luck :( – Calum 9 mins ago
A formula is the right way to go. You can use COUNTIF to check for the existence. Put this formula in cell M1 and pull it down.
=IF(COUNTIF($F$1:$F$14000,A1)>0,"Yes","No")
However if you still want to use code, try this (Untested)
Sub Sample()
Dim name1 As Variant, name2 As Variant, answer(1 To 14000) As String
Dim ws As Worksheet
Dim i As Long
With ThisWorkbook
name1 = .Worksheets("Sheet0").Range("A1:A14000").Value
name2 = .Worksheets("Sheet1").Range("F1:F14000").Value
For i = 1 To 14000
If IsInArray(name1(i, 1), name2) Then answer(i) = "Yes" Else answer(i) = "No"
Next i
.Worksheets("Sheet1").Range("M1").Resize(UBound(answer), 1).Value = _
Application.WorksheetFunction.Transpose(answer)
End With
End Sub
Function IsInArray(stringToBeFound As Variant, arr As Variant) As Boolean
Dim bDimen As Byte, i As Long
On Error Resume Next
If IsError(UBound(arr, 2)) Then bDimen = 1 Else bDimen = 2
On Error GoTo 0
Select Case bDimen
Case 1
On Error Resume Next
IsInArray = Application.Match(stringToBeFound, arr, 0)
On Error GoTo 0
Case 2
For i = 1 To UBound(arr, 2)
On Error Resume Next
IsInArray = Application.Match(stringToBeFound, Application.Index(arr, , i), 0)
On Error GoTo 0
If IsInArray = True Then Exit For
Next
End Select
End Function
You are first assigning a value to answer(i) from the Worksheet and then assigning the Yes value if it matches.
However this value gets assigned to answer() not to the Cell.
you need to change:
answer(i) = "yes"
to
ActiveWorkbook.Worksheets("Sheet0").Cells(i, 13).Value = "yes"
and remove answer() completely.
The better way would be this:
=IF(A1=F1;"Yes";"No")

Hiding columns in excel based on the value of a cell

My goal is to hide the column if all value from row 3 to 10 are zero in that column, so I create formula in the row 11 which is sum of the value from row 3 to 10
Basicly I can create code like this
If Range("B11").Value = 0 Then
Columns("B:B").EntireColumn.Hidden = True
Else
Columns("B:B").EntireColumn.Hidden = False
End If
If Range("C11").Value = 0 Then
Columns("C:C").EntireColumn.Hidden = True
Else
Columns("C:C").EntireColumn.Hidden = False
End If
but how to simply this, because I want to this macro run from Column B to FV,
or maybe any other solution to achieve my goal?
A well placed loop would help and the join function:
Dim X as Long
Columns("B:FV").EntireColumn.Hidden = False
For X = 2 To 178
If Join(Application.Transpose(Range(Range(Cells(3, X).Address & ":" & Cells(10, X).Address).Address).Value), "") = "00000000" Then Columns(X).Hidden = True
Next
Unhide ALL the columns first then you have removed the need for your else statement
Edit: With this solution, you also don't need your formula in row 11.
I have surprised no one write the easiest answer.
for i = 2 to 178
if cells(11, i).value = 0 then
Columns(i).EntireColumn.Hidden = True
end if
next
Heres one way.
Sub test()
Dim iStart As Long: iStart = Range("B1").Column
Dim iFin As Long: iFin = (Range("FV1").Column) - 1
Dim iCntCol As Long: iCntCol = iStart 'Col B is #2
For iCntCol = iStart To iFin 'FV is Col # 178
If Cells(11, iCntCol).Value = 0 Then
Columns(iCntCol).EntireColumn.Hidden = True
Else
Columns(iCntCol).EntireColumn.Hidden = False
End If
Next iCntCol
End Sub
HTH
should performance be an issue, consider what follows
Option Explicit
Sub hide()
Dim found As Range
With Intersect(ActiveSheet.Range("B11:FV11"), ActiveSheet.UsedRange.EntireColumn)
.EntireColumn.Hidden = False
.FormulaR1C1 = "=sum(R3C:R10C)"
Set found = GetZeroColumns(.Cells, 0)
End With
If Not found Is Nothing Then found.EntireColumn.Hidden = True
End Sub
Function GetZeroColumns(rng As Range, value As Variant) As Range
Dim firstAddress As String
Dim found As Range
With rng
Set found = .Find(What:=value, LookIn:=xlValues, lookat:=xlWhole)
If Not found Is Nothing Then
firstAddress = found.Address
Set GetZeroColumns = found
Do
Set GetZeroColumns = Union(GetZeroColumns, found)
Set found = .FindNext(found)
Loop While Not found Is Nothing And found.Address <> firstAddress
End If
End With
End Function
We could use a more versatile code to do this, by not hard coding the range of consideration, so that it can be reused in many places. Consider below, the For...Next loop will test each cell in Selection. Selection is the current selected cells. So just select the cells you want the code to run on. If a cell's value equals 0, then the column will be marked for hiding. I'd also not recommend hiding the column one-by-one, it makes the code unnecessarily slow, especially when there are a lot of formulas in the sheet or there are many columns to hide. So what i did is just mark the columns for hiding using the Union function. Then hide them at one go which you can see at the last line of the code.
Sub HideZerosByColumn()
Dim iRng As Range
Dim uRng As Range
Set uRng = Nothing
For Each iRng In Selection
If iRng = 0 And Not IsEmpty(iRng) Then
If uRng Is Nothing Then Set uRng = iRng Else Set uRng = Union(uRng, iRng)
End If
Next iRng
If Not uRng Is Nothing Then uRng.EntireColumn.Hidden = True
End Sub
Before running the code, select the range for consideration.
After running the code

Pass Range in Function, Sum Adjacent Cells, and Return Sum

I have the following Excel table:
I want to pass the first column as a string, determine the address of the cells called 'LNA' and 'LCAMP', and sum the adjacent cells 'between' those two addresses. My failed code:
Function LNAtoLCAMP(ComponentList) As Single
Dim i As Integer
Dim LBoundAddress As Variant, UBoundAddress As Variant
For i = LBound(ComponentList) To UBound(ComponentList)
If ComponentList(i, 1).Value = "LNA" Then
LBoundAddress = ComponentList(i, 1).Address.Offset(0, 1)
End If
If ComponentList(i, 1).Value = "LCAMP" Then
UBoundAddress = ComponentList(i, 1).Address.Offset(0, 1)
End If
Next
LNAtoLCAMP = Application.WorksheetFunction.Sum(LBoundAddress, ":", UBoundAddress)
End Function
Maybe there's a better way?
Try this:
Function LNAtoLCAMP() As Single
Dim LNA As Range, LCAMP As Range
With Sheets("Sheet1")
Set LNA = .Range("B:B").Find("LNA").Offset(0, 1)
Set LCAMP = .Range("B:B").Find("LCAMP").Offset(0, 1)
If Not LNA Is Nothing And Not LCAMP Is Nothing Then _
LNAtoLCAMP = Evaluate("SUM(" & .Range(LNA, LCAMP).Address & ")")
End With
End Function
Edit2: For your dynamic needs.
Function CONSUM(rng As Range, str1 As String, str2 As String, _
Optional idx As Long = 1) As Variant
Application.Volatile '<~~ autoupdate on cell change, remove otherwise
Dim r1 As Range, r2 As Range
Set r1 = rng.Find(str1, rng(1), , xlWhole)
Set r2 = rng.Find(str2, rng(1), , xlWhole, , xlPrevious)
If Not r1 Is Nothing And Not r2 Is Nothing Then _
CONSUM = Application.Sum(rng.Parent.Range(r1.Offset(0, idx), _
r2.Offset(0, idx))) Else CONSUM = CVErr(xlErrValue)
End Function
In the second function, you can select the range you search and also specify the string you want to search. It returns #VALUE! error if the strings you specify are not found. HTH.
For Edit2 offset is dynamic as well (default at 1). Also this will sum the first instance of the 1st string up to the last instance of the second which was raised by chrisneilsen.
Result:
According to your comment you are calling the function as
=LNAtoLCAMP(B16:B61)
This is not passing an array, it is passing a range (that's a good thing)
Your function, modified:
Function LNAtoLCAMP(ComponentList As Range) As Variant
Dim i As Long
Dim dat As Variant
Dim Sum As Double
Dim LBoundAddress As Long, UBoundAddress As Long
dat = ComponentList.Value
For i = LBound(dat, 1) To UBound(dat, 1)
Select Case dat(i, 1)
Case "LNA", "LCAMP"
If LBoundAddress = 0 Then
LBoundAddress = i
End If
If i > UBoundAddress Then
UBoundAddress = i
End If
End Select
Next
For i = LBoundAddress To UBoundAddress
Sum = Sum + dat(i, 2)
Next
LNAtoLCAMP = Sum
End Function
Call it with both columns in the range
=LNAtoLCAMP(B16:C61)
Note:
I have assumed you want to include hidden rows in the sum, and "Between" includes the rows LNA and LCAMP are on. Both these assumptions are easily modified if required.
I have also assumed you want to sum from the first instance of either string to the last instance of either string. Also easily modified if required.
You could also pass in the search strings to make it more flexable.
You should add error handling, eg if one of the search string is not in the list
If you insist on using an ApplicationFunction then you need quotes.
Also I believe it should be .offset().address (Needs to be flipped)
TESTED WORKING:
Function LNAtoLCAMP(ByVal ComponentList As Range) As Single
Dim LBoundAddress As Range, UBoundAddress As Range
Dim cel As Range
For Each cel In ComponentList
If cel.Value = "LNA" Then
Set LBoundAddress = cel.Offset(0, 1)
End If
If cel.Value = "LCAMP" Then
Set UBoundAddress = cel.Offset(0, 1)
End If
Next cel
LNAtoLCAMP = Application.WorksheetFunction.Sum(Range(LBoundAddress, UBoundAddress))
End Function