VBA len function not passing length - vba

I am trying to grab a cell check if it has decimal places and remove them then place a specific format in a cell depending on how many characters there are in the number, the len function returns null, and the instr function works but when passed to a variable returns null. Thank you to anyone who can help. At the end of the first if function I print the results of the 3 variables not working to the immediate window to verify, with the Debug.Print command please go to view menu and activate immediate window to watch.
Function cnvtDta()
ActiveSheet.Select
Data1 = Range("data").Value
Dim rslt As String
rslt = Data1
Set myrng = Range("data")
Dim wot, sowot
'Find decimal place in cell
dot = myrng.Find(".", myrng)
If dot = True Then
'if decimal place strip remainders and decimal point
Dim pos, res
pos = InStr(1, rslt, ".")
res = Left(rslt, pos)
sowot = Len(res)
End If
Debug.Print res
Debug.Print sowot
Debug.Print pos
'Return specific formats to cell
'thank you kindly to anyone who can spare the time to genuinely help
End Function

So basically there's a couple of parts to your question.
Check if value has decimals. Here's one way to do it (based on values, not on strings)
Function DoesCellContainDecimals(inputRange As Range) As Boolean
Dim tolerance As Double
tolerance = 0.0001
If Not IsNumeric(inputRange.Value2) Then
'invalid argument
DoesCellContainDecimals = False
Exit Function
End If
If (Abs(Fix(inputRange.Value2) - inputRange.Value2) < tolerance) Then
'value does not have meaningful decimals
DoesCellContainDecimals = False
Else
'value has meaningful decimals
DoesCellContainDecimals = True
End If
End Function
Get the integer part of a number. There are two functions. Similar but different behavior with negative numbers (make sure if the value is a number first):
Int(6.5) '6
Fix(6.5) '6
Int(-6.5) '-7
Fix(-6.5) '-6
Format a number. Either turn it to string or set Range.NumberFormat property:
Format(6500000,"# ### ###") '6 500 000
Range("A1").NumberFormat = "# ### ##0" 'same effect as above but only when displaying in that cell.

Related

Parsing a function for its arguments

I have a UDF in written in VBA, which I call from my sheet. The function takes 3 arguments: Function CONCATIF(arg1 As Range, arg2 As Boolean, Optional arg3 As Range) As Variant
The UDF needs to know the formula of arg2, i.e. to intercept arg2 before it has been evaluated to TRUE or FALSE. To do this I use Application.Caller.Formula, which gives me (in its simplest form) "=CONCATIF(arg1, arg2, arg3)" (or in place of ,arg3) either ) or,))
I can then get arg 2 by simply using Split(Application.Caller.Formula, ",")
There are several problems I'd like to deal with though
Splitting at commas means that none of my arguments can contain commas, which they may have to
The formula may be nested, e.g =SUM(1,IF(CONCATIF(arg1, arg2, arg3)="a",1,0)), so I don't know which item of my split array is arg2. (I think this should be fairly easy to fix: find CONCATIF in the string and chop off the start, count the open/close brackets which follow it until open = close, and then chop off the end.
The arguments may be formulae themselves; arg1 could be a reference to a range, not an actual range.
Tricky: CONCATIF may come up multiple times in 1 formula, but with standard string searches I'll always pick up the first one (I may just have to return an error if there are multiple in 1 formula, as I can't think how to get around this at all)
So what I want: A generic way of obtaining the correct CONTCATIF() formula from the caller cell, and then parsing out the three arguments as 3 strings in an array. For reference, here's my code (sorry, naming is a little different from question)
Public Function CONCATIF(checkRange As Range, testFunction As Boolean, Optional concatRange As Range) As Variant
Dim concatArray() As Variant
Dim formulaText As String, formulaParts() As String, formulaTest As String
Dim topLeft As Range, subCell As Range
Dim newTest As String
Dim results() As Boolean, result As Boolean
Dim loopval As Long
'''
'input checking
'''
If concatRange Is Nothing Then
concatArray = checkRange
ElseIf Not (checkRange.Cells.Count = concatRange.Cells.Count And checkRange.Rows.Count = concatRange.Rows.Count And checkRange.Rows.Count = 1) Then
CONCATIF = CVErr(xlErrValue)
Exit Function
Else
concatArray = concatRange.Value2
End If
'''
'Extract test function
'''
formulaText = Application.Caller.Formula
formulaParts = Split(formulaText, ",") 'Assumes 1)no commas 2) formula isn't nested 3) formula doesn't contain nested functions
formulaTest = formulaParts(1) 'get the test function as a string to be evaluated
Set topLeft = checkRange.Cells(1, 1) 'This is the 'reference' cell - substitute each of the values in the check range for this to test each one
ReDim results(0)
On Error GoTo Err
'''
'Run test on each of the cells in checkRange
'''
For Each subCell In checkRange
newTest = Replace(formulaTest, topLeft.Address(0, 0), subCell.Address)
If Count(newTest, "(") < Count(newTest, ")") Then 'when optional parameter is missed out, sometimes you get a ,) and sometimes a ) after formulaTest, so we must check
newTest = Left(newTest, Len(newTest) - 1)
End If
result = (Evaluate(newTest))
skip:
results(UBound(results)) = result
ReDim Preserve results(UBound(results) + 1)
Next subCell
'''
'Then use array of Booleans for UDF function
'''
CONCATIF = "test"
Exit Function
Err:
result = False 'if the evaluate results in an error, it means the input was invalid, so probably won't meet the criteria, therefore can be ignored
loopval = loopval + 1
If loopval > checkRange.Cells.Count Then CONCATIF = CVErr(xlErrNA): Exit Function 'exit error loop gracefully if I've missed some edge case
Resume skip
End Function
And then referenced in my UDF is this:
Function Count(str As String, chr As String) As Long 'counts the number of instances of a character in a string
Count = Len(str) - Len(Replace(str, chr, ""))
End Function
If you had a proper formula parser you could solve all of these problems except the one of handling multiple calls to CONCATIF in a single formula: I don't know of a way of finding out 100% which instance of CONCATIF is currently being called.
There are various Formula parsers around you could maybe adapt: start here
http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html

How to add decimal and remove text from alphanumerical string

I have a huge amount of data which is alphanumerical and I need to convert it to purely numerical. Which no text in the string.
Ex.
C0424.100 ---> 424.100 (or 0424.100)
There always is 3 places after the decimal. Any tips on how to go about this? I'm pretty new to VBA. So basically I need to remove all text and a decimal with three digits to the right of it.
This is well described in String functions and how to use them
However, this should get you started. I would handle the formatting in Excel afterwards, but this is the simple string to number conversion. If the strings are more complex, consider using the Search string function to find the numbers, then use Right, Left, Mid functions to trim the string. Lastly use the CDbl() function to convert the string to the double.
Macro code as follows:
Sub temp()
'
' temp Macro
Range("A2").Select
stringToConvert = Selection.Value
trimmedString = Right(stringToConvert, Len(stringToConvert) - 1)
numberToDisplay = CDbl(trimmedString)
Range("A3").Value = numberToDisplay
End Sub
Do you even need VBA? If your data always has just one leading alpha character then you can just use standard Excel functions. For an entry in A2 that you want to convert, place the following formula in a convenient cell (e.g. B2):
=VALUE(RIGHT(A2,LEN(A2)-1))
I got UDF options for you.
Option 1: If you want to remove all the alphas from the beginning of string:
Function RemoveFirstAlphas(txt As String) As String
Dim i As Long
For i = 1 To Len(txt)
Select Case Mid$(txt, i, 1)
Case "0" To "9": Exit For
Case Else: Mid$(txt, i, 1) = Chr(32)
End Select
Next
RemoveFirstAlphas = Trim(txt)
End Function
Option 2: If you want to remove all the alphas from entire string:
Function RemoveAllAlphas(txt As String) As String
Dim ObjRegex As Object
Set ObjRegex = CreateObject("vbscript.regexp")
With ObjRegex
.Global = True
.Pattern = "[a-zA-Z\s]+"
RemoveAllAlphas = .Replace(Replace(txt, "-", Chr(32)), vbNullString)
End With
End Function
No need for VBA. Something like:
=--MID(A1,MIN(FIND({0,1,2,3,4,5,6,7,8,9},A1&"0123456789")),99)
will return the string starting with the first digit, and convert it to a numeric value. You can then format it in the cell however you wish.
The above will work with any number of non-digit leading characters. If will only have a single non-digit character, then #Skippy answer is simpler
If you have to have a VBA routine, something like the following should work -- it will extract the first numeric substring in the string. It does not matter if there are non-digits before or after. And, if there are no digits, the function will return the #NUM! error
Option Explicit
Function ExtractNums(S As String) As Variant
Dim I As Long
For I = 1 To Len(S)
If IsNumeric(Mid(S, I, 1)) Then
ExtractNums = Val(Mid(S, I))
Exit Function
End If
Next I
ExtractNums = CVErr(xlErrNum)
End Function

Validating user input excel vba

I have the following user input set up in excel vba. The function when called asks the user to input a single number no greater than 9999, or two numbers in the format XXXX-XXXX where two numbers are separated by a dash. In that case, the numbers cannot be greater than 9999 in either case, or at least shouldn't.
The goal is to return either a single number (IE 50) or a range (IE low value is 50,high value is 75). Currently as set up it should be returning an array, where the first position is the low value, and the second position is high value. Or, if the user only enters one number, it should return that one number in the first position of an array.
Currently it checks that A) user has entered in a number, B) the number is not greater than 4 digits long.
Unfortunately it is not returning an array, it is returning an error. Subscript out of range.
Also, are there any other likely user inputs that should be checked for here? The application is not going to be widely used by people, but I'd like to minimize potential errors as well.
Public Function getUserInput() As Variant
'this function gets a user input from an input box and puts it out into the proper format
Dim inputString As String
Dim numArr() As String
Dim i As Long
' On Error GoTo NotValidInput
inputString = Trim(InputBox("Enter the rows you'd like to print"))
'has the user entered a dash into their user input
If InStr(inputString, "-") > 0 Then
numArr() = Split(inputString, "-")
If UBound(numArr) <> 1 Then
GoTo NotValidNumberFormat
End If
If (IsNumeric(numArr(0)) And Len(numArr(0)) <= 4) And (IsNumeric(numArr(1)) And Len(numArr(1)) <= 4) Then
getUserInput = numArr
Exit Function
Else
GoTo NotValidNumberFormat
End If
'no dash
'60
Else
If (IsNumeric(CInt(inputString))) And Len(inputString) <= 4 Then
getUserInput = numArr
Exit Function
Else
GoTo NotValidNumberFormat
End If
End If
Exit Function
NotValidNumberFormat:
'if the conversion failed, return error
MsgBox ("Please enter the number in a valid format - either a single number no larger than 9999 or two numbers no larger than 9999 separated by only one dash (IE XX-XX)")
getUserInput = -1
End Function
this should do:
Public Function getUserInput() As Variant
'this function gets a user input from an input box and puts it out into the proper format
Dim numArr As Variant
Dim goOn As Boolean
Do
numArr = Split(WorksheetFunction.Trim(InputBox("Enter the rows you'd like to print in the format 'nnnn' or 'nnnn-mmmm'")), "-")
Select Case UBound(numArr)
Case 0
goOn = Format(numArr(0), "0000") Like "####"
Case 1
goOn = Format(numArr(0), "0000") Like "####" And Format(numArr(1), "0000") Like "####"
End Select
If Not goOn Then MsgBox "Please enter the number in a valid format - either a single number no larger than 9999 or two numbers no larger than 9999 separated by only one dash (ex: XX-XX)"
Loop While Not goOn
getUserInput = numArr
End Function

Excell cell value is not read as Number?

I am trying to add the data in the two cells of the excel sheet but even if the excel cell is of the type number it does not add up the cells. It seems that there is space infornt of the number that it does not add....image is below.
Is there a vba code to remove this space from each of the cell if its presesnt.
I have exported the excel from a pdf.
Excel will attempt to convert any value to a number if you apply an operator to it, and this conversion will handle spaces. So you can use =A1*1 or A1+0 to convert a value in A1 to a number, or something like this within a function =SUM(IFERROR(A1*1,0)).
That kind of implicit conversion automatically performs a trim(). You can also do this conversion explicitly by using the funciton N(), or NumberValue() for newer versions of Excel. However, as others have pointed out, many characters won't be automatically handled and you may need to use Substitute() to remove them. For instance, Substitute(A1,160,"") for a non-breaking space, a prime suspect because of its prevalence in html. The Clean() function can give you a shortcut by doing this for a bunch of characters that are known to be problematic, but it's not comprehensive and you still need to add your own handling for a non-breaking space. You can find the ASCII code for any specific characters that are grieving you by using the Code() function... for instance Code(Mid(A1,1,1))
Character Handling UDF
The UDF below gives flexibility to the character handling approach by allowing multiple characters to be removed from every cell in a range, and produces a result that can be used as an argument. For example, Sum(RemoveChar(A1:A5,160)) would remove all non-breaking spaces from the range being summed. Multiple characters can removed by being specified in either a range or array, for example Sum(RemoveChar(A1:A5,B1:B3)) or Sum(RemoveChar(A1:A5,{160,150})).
Function RemoveChar(R As Range, ParamArray ChVal() As Variant)
Dim x As Variant
Dim ResVals() As Variant
ReDim ResVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
x = R(j).Value2
If x <> Empty Then
'Try treating character argument as array
'If that fails, then try treating as Range
On Error Resume Next
For i = 1 To UBound(ChVal(0))
x = Replace(x, Chr(ChVal(0)(i)), "")
Next
If Err = 92 Then
Err.Clear
For Each Rng In ChVal(0)
x = Replace(x, Chr(Rng.Value2), "")
Next
End If
Err.Raise (Err)
On Error GoTo 0
'If numeric then convert to number
'so that numbers will be treated as such
'when array is passed as an argument
If IsNumeric(x) Then
ResVals(j) = Val(x)
Else
ResVals(j) = x
End If
End If
Next
'Return array of type variant
RemoveChar = ResVals
End Function
Numeric Verifying UDF
The drawback with replacing characters is that it's not comprehensive. If you want something that's more of a catch-all, then perhaps something like this.
Function GetNumValues(R As Range)
Dim c, temp As String
Dim NumVals() As Double
ReDim NumVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
'Loop through characters
'Allow for initial short-circuit if already numeric
For i = 1 To Len(R(j).Value2)
c = Mid(R(j).Value2, i, 1)
'If character is valid for number then include in temp string
If IsNumeric(c) Or c = Application.DecimalSeparator Or c = Application.ThousandsSeparator Then
temp = temp + c
End If
Next
'Assign temp string to array of type double
'Use Val() function to convert string to number
NumVals(j) = Val(temp)
'Reset temp string
temp = Empty
Next
'Return array of type double
GetNumValues = NumVals
End Function

Unexpected String Results

I have the following code to check values entered into two input boxes, if both values are zero then the MsgBox should display "Stop!" (I will change this later to exiting the sub but I am using a MsgBox for testing)
From testing I've seen these results:
A zero in both strings produces the expected message box.
A non zero in the first string followed by any non zero value in the second string does nothing (as expected).
A zero in the first string followed by a second string value equal to or greater than 10 produces the message box (unexpected).
I've also noticed that if the second string is 6-9 it is displayed as x.00000000000001%. I think this is a floating point issue and could be related? This behaviour occurs without the IF... InStr function too.
Option Explicit
Sub Models()
Dim MinPer As String, MaxPer As String, Frmula As String
Dim Data As Worksheet, Results As Worksheet
Set Data = Sheets("Data")
Set Results = Sheets("Results")
Application.ScreenUpdating = False
MinPer = 1 - InputBox("Enter Minimum Threshold Percentage, do not include the % symbol", _
"Minimum?") / 100
MaxPer = 1 + InputBox("Enter Maximum Threshold Percentage, do not include the % symbol", _
"Maximum?") / 100
If (InStr(MinPer, "0") = 0) And (InStr(MaxPer, "0") = 0) Then
MsgBox "STOP!"
End If
' Remainder of code...
This is the most interesting problem I've come across so far in VBA and welcome any discussion about it.
Edit: I use this code to display on screen the paramaters for the end-user to see. Hence how I noticed the .00000000001% issue:
.Range("D2").Value = "Min is " & 100 - MinPer * 100 & "%"
.Range("D3").Value = "Max is " & MaxPer * 100 - 100 & "%"
Two things
1) Declare MinPer, MaxPer as Long or a Double and not a String as you are storing outputs from a calculation
2) Don't directly use the InputBox in the calculations. Store them in a variable and then if the input is valid then use them in the calculation
Dim MinPer As Double, MaxPer As Double, Frmula As String
Dim Data As Worksheet, Results As Worksheet
Dim n1 As Long, n2 As Long
Set Data = Sheets("Data")
Set Results = Sheets("Results")
Application.ScreenUpdating = False
On Error Resume Next
n1 = Application.InputBox(Prompt:="Enter Minimum Threshold Percentage, do not include the % symbol", _
Title:="Minimum?", Type:=1)
On Error GoTo 0
If n1 = False Then
MsgBox "User cancelled"
Exit Sub
End If
On Error Resume Next
n2 = Application.InputBox(Prompt:="Enter Maximum Threshold Percentage, do not include the % symbol", _
Title:="Maximum?", Type:=1)
On Error GoTo 0
If n2 = False Then
MsgBox "User cancelled"
Exit Sub
End If
If n1 = 0 And n2 = 0 Then
MsgBox "STOP!"
End If
MinPer = 1 - (Val(n1) / 100)
MaxPer = 1 + (Val(n2) / 100)
This is because the number "10" has a "0" in the string (second character) so both evaluate to true.
Try this instead:
If (MinPer = "0") And (MaxPer = "0") Then
MsgBox "STOP!"
End If
For additional control save the user input (MinPer , MaxPer) and THEN text them for validity before performing nay mathematical operations on them.
InStr(MinPer, "0") is just checking to see whether the string contains a zero
character.
You need to convert the string value to an integer. Use the IsNumeric and CInt functions
to do that. See this URL:
vba convert string to int if string is a number
Dim minPerINT as Integer
Dim maxPerINT as Integer
If IsNumeric(minPer) Then
minPerINT = CInt(minPer)
Else
minPerINT = 0
End If
If IsNumeric(maxPer) Then
maxPerINT = CInt(maxPer)
Else
maxPerINT = 0
End If
If minPerINT = 0 and maxPerINT=0 Then
MsgBox "STOP!"
End If
Depending on what data can be entered It may also be a good idea to check if the length
of the data is zero using the len() function.