Determining if Cancel Button pressed where InputBox variable declared as Double - excel

I have my variable declared as a double so I can perform mathematical operations on it.
I am trying to determine when the cancel button is pressed.
Dim thckmax As Double
thckmax = InputBox("What is the maximum nominal thickness?", "Enter Max Nominal Thickness Measurement")
If thckmax = 0 Then
GoTo Line3
End If
thckmin = InputBox("What is the minimum nominal thickness?", "Enter Min Nominal Thickness Measurement")
If thckmin = 0 Then
GoTo Line3
End If
thcknom = (thckmax + thckmin) / 2
Worksheets(1).Range("C" & cols + 2).Value = thcknom
.
.
.
Line3: ...
I know I used GoTo. It was a quick and easy fix to get the code up and running.
I get Run-Time error 13 type mismatch. I have also tried CDbl(...),StrPtr(...),IsEmpty(...) and instead of setting them equal to zero I have also tried
If thckmax = "" Then
GoTo Line3
End If`
I cannot get anything to work as far as determining if cancel was pressed and going to Line3:.
All posts I found indicate the declared variable as a string, mine is a double.

You could try something like this to test whether or not the input box was canceled and if it is numeric.
Dim thckmaxTest As String
Dim thckmax As Double
thckmaxTest = InputBox("What is the maximum nominal thickness?", "Enter Max Nominal Thickness Measurement")
If thckmaxTest <> vbNullString and IsNumeric(thckmaxTest) then thckmax = CDbl(thckmaxTest)
If thckmax = 0 Then
GoTo Line3
End If
Dim thckminTest As String
Dim thckmin As Double
thckminTest = InputBox("What is the minimum nominal thickness?", "Enter Min Nominal Thickness Measurement")
If thckminTest <> vbNullString and IsNumeric(thckmibTest) then thckmin = CDbl(thckminTest)
If thckmin = 0 Then
GoTo Line3
End If
thcknom = (thckmax + thckmin) / 2
Worksheets(1).Range("C" & cols + 2).Value = thcknom

As is noted in Microsoft's documentation, the InputBox function returns a string.
How then, you may ask, can you sometimes store the return value from an InputBox to an integer value? Because of implicit coercion. Basically, if you try to store a value in an incompatible variable, VBA attempts to coerce the value to the right data type. If you try to store a string value into a Double variable, VBA attempts to coerce the string into the right data type. This occurs whether you use InputBox or a hard-coded string. For example, the following snippets are equivalent:
Dim x as Double
x = "5"
''''''''''''''''''''''''''''''''''''''
Dim x As Double
x = InputBox("Enter a number")
' user enters 5
When using InputBox, you of course have no control over whether the user enters valid input. Which is why (as #Dude_Scott mentions), you should store user input into a string, and then make sure you have the right value.
If the user clicks Cancel in the inputbox, the empty string is returned (as per documentation above). Since the empty string can't be implicitly coerced to a double, an error is generated. It's the same thing that would happen if the user entered "apples" in the InputBox.
As #Dude_Scott notes, you should use IsNumeric (or something similar) to make sure that the user input is what you need. However, you don't need to include a check for a null or empty string (since IsNumeric returns False for those values). So you really just need something like this:
Public Sub foo()
Dim xDouble As Double, xString As String
xString = InputBox("Enter a number")
If IsNumeric(xString) Then
xDouble = CDbl(xString)
MsgBox xDouble
Else
MsgBox "Invalid number"
End If
End Sub
For more information about coercion, see the following Microsoft articles:
Conversions Between Strings and Other Types
Implicit and Explicit Conversions

All others I have found indicate the declared variable as a string, mine is a DOUBLE
You can't have a Double. The function returns a String, and you can't change that. Using a Double to capture the result will cause problems sooner or later.
What I was trying to explain in a comment box earlier, is that there's a possibility that 0 might be a valid input, so immediately converting the input into a Double is preventing you from being able to tell cancellation from a legit 0 - regardless of the type mismatch error that's guaranteed to happen whenever the resut isn't numeric.
As other answers show, this involves quite a bit of plumbing: enough to warrant being pulled into its own dedicated wrapper function.
The problem is that functions return one value, so you can return a Double and pick a specific "magic value" to mean "input was cancelled", but that's a poor practice.
A better way is to make the wrapper function return a Boolean, and leverage a ByRef parameter to return the result - a function like this returns False if the user cancels the prompt, True if the prompt was not cancelled, and outResult will be either 0 for a non-numeric input, or the input converted to a Double:
Public Function TryGetDoubleInput( _
ByVal prompt As String, _
ByVal title As String, _
ByVal default As String, _
ByRef outResult As Double) _
As Boolean
Dim result As String
result = VBA.Interaction.InputBox(prompt, title, default)
TryGetDoubleInput = StrPtr(result) <> 0 'return false if cancelled
If IsNumeric(result) Then outResult = CDbl(result)
End Function
Can be used like this:
Dim value As Double
If TryGetDoubleInput("Enter a numeric value:", "Prompt", "0.00", value) Then
If value = 0 Then
MsgBox "You entered either 0 or a non-numeric value"
Else
MsgBox "You entered " & CStr(value) ' note the irony
End If
Else
MsgBox "You cancelled the prompt"
End If
Now if you need to treat invalid values differently than 0 (i.e. if 0 is a legit input), consider throwing an error:
Public Function TryGetDoubleInput( _
ByVal prompt As String, _
ByVal title As String, _
ByVal default As String, _
ByRef outResult As Double) _
As Boolean
Dim result As String
result = VBA.Interaction.InputBox(prompt, title, default)
If StrPtr(result) = 0 Then Exit Function 'return false if cancelled
If IsNumeric(result) Then
outResult = CDbl(result)
TryGetDoubleInput = True
Else
Err.Raise 555, "TryGetDoubleInput", "Non-numeric input is invalid."
End If
End Function
And now you can use error handling to handle invalid inputs, and you can now tell a legit 0 from a cancelled inputbox from an arbitrary invalid input:
On Error GoTo ErrHandler
Dim value As Double
If TryGetDoubleInput("Enter a numeric value:", "Prompt", "0.00", value) Then
MsgBox "You entered " & CStr(value) ' note the irony
Else
MsgBox "You cancelled the prompt"
End If
Exit Sub
ErrHandler:
MsgBox Err.Description ' "Non-numeric input is invalid."

Related

VBA Inputbox to Prevent Decimals

I am trying to develop a tool that will help standardize a description catalog of products. I want to have an input box prompt a user to enter a size. I want to encourage size entries like "5-1/2" and prevent users from entering "5.5". Ideally, if the size was entered with a decimal and not a dash with a fraction, I want a message box to pop up saying they can not do that. It would then need to re-show the input box.
Here is what I have -
Private Sub CS_Other_Click()
Unload Me
Sheets("Fill In").Activate
Worksheets("Fill In").Range("C2").NumberFormat = "#"
Dim other_casing_size As Variant
other_casing_size = InputBox("Fill in the casing size. Syntax MUST be in the form of X-X/X", "New Casing Size")
Range("C2") = other_casing_size
I just dont know the code to prevent an entry with decimals. Even better, if i knew how to code an exact syntax to include or exclude anything I wanted that would be perfect.
Thanks
A while loop, which checks the input string for a dot or comma would work quite ok, I guess:
Sub TestMe()
Dim inputString As String
Dim inputNumeric As Boolean
inputString = InputBox("Please, enter a number!")
inputNumeric = isNumeric(Evaluate(inputString))
Do While InStr(1, inputString, ".") Or _
InStr(1, inputString, ",") Or _
Not inputNumeric
If Not CBool(inputNumeric) Then
MsgBox "You tried to cancel or entered empty value!"
Exit Do
End If
MsgBox "Please, do not write dot or comma!"
inputString = InputBox("Please, enter a number!")
inputNumeric = isNumeric(Evaluate(inputString))
Loop
End Sub
The isNumeric() checks the input for being able to be converted to numeric. Thus 5-1/2 should be ok.
Concerning cancellation or entering empty value from the InputBox() - it really depends on the business logic of the "app", but in the case above - there is a msgbox and it exits the loop.
Write a separate function responsible for that prompt, and use it e.g. like this:
Dim casingSize As String
If GetCasingSize(casingSize) Then
ActiveSheet.Range("C2").Value = casingSize
End If
The function needs to return a Boolean for this to work - it returns True if the input is valid, False if there's no valid input to work with (e.g. prompt was cancelled). What makes this work, is passing the result as a ByRef argument, like this:
Public Function GetCasingSize(ByRef outResult As String) As Boolean
Do
Dim raw As Variant
raw = InputBox("Casing size?")
If VarType(raw) = vbBoolean Then
'handle cancelled prompt:
Exit Do
End If
If ValidateFractional(raw) Then
'handle valid input:
outResult = CStr(raw)
GetCasingSize = True
Exit Do
End If
'handle invalid input:
If MsgBox("The value '" & raw & "' is not valid. Try again?", vbYesNo) = vbNo Then
Exit Do
End If
Loop
End Function
Note the ValidateFractional function is its own concern - a separate, Private function would work, but I'd recommend making it Public, and unit-testing it to make sure it works as intended given a wide variety of edge-case inputs - and having it in a separate function means the logic in GetCasingSize doesn't need to change if the validation needs to be fine-tuned; for example this naive implementation uses the Like operator and would work for 5-1/4, but not for e.g. 15-5/8:
Public Function ValidateFractional(ByVal value As String) As Boolean
ValidateFractional = value Like "#[-]#/#"
End Function
Using Regular Expressions for this would probably be a good idea.

Why won't Excel VBA accept my user defined variable

I have recently written some code that finds a value in a worksheet. If said value exists, it offsets the active cell to the first row. If the value doesn't exist, it displays an error message. I'm having an issue where it is rejecting my variable because of either a "runtime 91 error" or a compile error that requires an object. I am new to VBA, per chance might anyone know what this error is asking for. Below is my code for facilitated viewing.
Private Sub CommandButton1_Click()
Dim IDNUM As Boolean
Set IDNUM = Worksheets("Petrobras").Range("V:V").Find(TXTOPPNUM_Insert.Value, , , LookAt:=xlWhole).Select
If IDNUM = False Then
MsgBox "This Opportunity has Not Been Registered Yet"
Else
ActiveCell.Activate
ActiveCell.Offset(0, -21).Activate
End If
End Sub
Dim IDNUM As Boolean
This declares the identifier IDNUM and allocates it enough memory to store a Boolean value for it. Note that the possible Boolean values are True and False.
Set IDNUM = ...
This is a Set assignment, which means VBA will be expecting the left-hand side of the assignment operator (that's =) to be a reference type (i.e. an object reference). But IDNUM is a Boolean, so the Set assignment is illegal.
You want this to be a value assignment:
Let IDNUM = ...
But then, the Let keyword is redundant/obsolete, so you can just do:
IDNUM = ...
Now, the right-hand side of the assignment is also problematic:
Worksheets("Petrobras").Range("V:V") _
.Find(TXTOPPNUM_Insert.Value, , , LookAt:=xlWhole) _
.Select
First, the expression has no defined type: the (Range).Select method returns no value, so the expression can't legally appear to the right of an = operator.
(Range).Find does return something though - but it returns a Range object reference, and when it can't find what it's looking for, it returns Nothing - a special reference value that basically means "there's no object here", and any member call (like .Select) made against Nothing will always raise run-time error 91. NEVER assume Range.Find will return a valid object reference.
Instead, capture the search result into an object variable:
Dim findResult As Range
Set findResult = Worksheets("Petrobras").Range("V:V").Find(TXTOPPNUM_Insert.Value, LookAt:=xlWhole)
Note that because you're using named arguments, you don't need to specify the optional empty positional ones. That said Range.Find will use unspecified defaults for every single one of the optional parameters you're not specifying, so the recommendation is to ALWAYS provide every single one of them - otherwise you're at the mercy of whatever the user did last time they hit Ctrl+F.
IF the returned object isn't Nothing, then you can use it:
If Not findResult Is Nothing Then
IDNUM = findResult.Value 'not sure what you mean to do here
Else
'no found. now what?
Exit Sub
End If
Lastly, note that If {bool-expression} = {True|False} Then is redundant: a Boolean value is a Boolean expression, so the comparison to a True or False literal is entirely redundant. Use the Not logical operator instead of comparing to False:
If Not IDNUM Then
MsgBox "This Opportunity has Not Been Registered Yet"
Else
ActiveCell.Offset(0, -21).Activate
End If
...and consider avoiding negatives by putting the "nope" case last and reversing the condition:
If IDNUM Then
ActiveCell.Offset(0, -21).Activate
Else
MsgBox "This Opportunity has Not Been Registered Yet"
End If
Note that the ActiveCell is already active - ActiveCell.Activate does nothing.
...and you don't need to care for the ActiveCell at all - the cell you want is the findResult range:
If IDNUM Then
findResult.Offset(0, -21).Activate
Else
MsgBox "This Opportunity has Not Been Registered Yet"
End If
Recap:
Dim findResult As Range
Set findResult = Worksheets("Petrobras").Range("V:V").Find(TXTOPPNUM_Insert.Value, LookAt:=xlWhole)
If Not findResult Is Nothing Then
If CBool(findResult.Value) Then
findResult.Offset(0, -21).Activate
Else
MsgBox "This Opportunity has Not Been Registered Yet"
End If
End If
End If

Validate Input box entry between min and max number

I used InputBox for the "period" which is the month ("m").
When I try using it for the "year" ("YYYY") it does not function as expected.
My code for the "year" (same as in "period", only different values and variables):
Dim VYear as variant
Dim defY as variant
defY = Format(DateAdd("YYYY", 0, Now), "YYYY")
VYear = InputBox("Year covered","Year",defY)
If VYear > 2014 And VYear < defY Then
Range("I1").Value = VYear
ElseIf VYear = "" Then
Exit Sub
Else
Do Until VYear > 2014 And VYear < defY
MsgBox "Please enter a year not earlier than 2015 and not later than this year"
VYear = InputBox("Year covered")
Loop
End If
It does give me the default value of 2018. When I tried entering wrong values, it proceeds with the message in MsgBox as expected, but it would no longer accept ANY values, even the year "2018".
Cycle goes: MsgBox (Please enter....) then InputBox then MsgBox again.
I intentionally used As Variant so that even though users input letters, it won't give the error of "type mismatch".
It should look like this …
Option Explicit
Public Sub AskForYear()
Dim InputValue As Variant 'needs to be variant because input is FALSE if cancel button is pressed
Dim DefaultYear As Integer
DefaultYear = Year(Date) 'Get the year of the current date today
Do
InputValue = Application.InputBox(Prompt:="Please enter a Year between 2015 and " & DefaultYear & "." & vbCrLf & "Year covered:", Title:="Year", Default:=DefaultYear, Type:=1)
If VarType(InputValue) = vbBoolean And InputValue = False Then Exit Sub 'cancel was pressed
Loop While InputValue < 2015 Or InputValue > DefaultYear
Range("I1").Value = InputValue 'write input value
End Sub
It uses a Do loop that is at least run once (note the criteria is in the Loop While part). It keeps asking for a date between 2015 and this year until the criteria is met. Then it will continue to write into the range.
Note that there is a cancel criteria to catch if the user pressed the cancel button. Then it exits the sub without writing into the range.
This is just hypothetical for your specific case (asking for a year) but …
For this criteria it is not sufficient to test If InputValue = False if you plan to accept 0 as number input. Therefore you need also to test for Booleantype:
If VarType(InputValue) = vbBoolean
This is because False is automatically cast to 0.
Note that I used Application.InputBox instead of InputBox. These are 2 completely different ones:
Application.InputBox(Prompt, Title, Default, Left, Top, HelpFile, HelpContextID, Type)
'see the different parameters
InputBox(Prompt, Title, Default, XPos, YPos, HelpFile, Context) As String
In Application.InputBox you can provide a Type parameter which I set to 1 which means it only accepts numbers (see Application.InputBox Method). With just InputBox you cannot do this.
I recommend to use meaningful variable names, which makes it much easier to read and maintain the code. Also only use Variant if really necessary. In this case it is, because the Application.InputBox can either return a Boolean (cancel button) or number (input).
Another recommendation is to always specify a worksheet for a Range like Worksheets("Sheet1").Range("I1") otherwise Excel guesses which worksheet you mean and it might easily fail.

Handling non integer in inputbox in VBA

I have a variable called "need" that is defined as an integer. An input box come up and prompts the user. If they type an integer it displays Msgbox "Got your number". If I type a string I get Runtime error '13': type mismatch. I thought if I just used an Else statement, it would say try again. It is not doing that though. Do I need error handling in the Else statement? And if so, what would the line(s) be?
Sub gadgetmanuf()
Dim need As Integer
'Dim rawneed As Single
'Dim rawavailable As Single
need = InputBox("How many gadgets are needed?", "Insert a number")
If TypeName(need) = "Integer" Then
MsgBox ("Got your number")
Else
MsgBox ("Try again")
End If
End Sub
Using an Application.InputBox with type 1 forces the user to enter a number (provides its own error message for text, ranges etc). So all you need to handle is the Cancel option, ie
The code below uses a variant to handle this, as using Cancel with an Integer or Long gives 0 - which could be a valid entry.
Sub TaylorWalker()
redo:
vStr = Application.InputBox("How many gadgets are needed?", "Enter a number", , , , , , Type:=1)
If vStr = False Then GoTo redo
End Sub
longer option
Test that the entered variable is greater than 0
Sub EddieBetts()
Dim StrPrompt As String
Dim lngNum As Long
StrPrompt = "How many gadgets are needed?"
redo:
lngNum = Application.InputBox(StrPrompt, "Enter an integer number (numbers will be rounded)", , , , , , Type:=1)
If lngNum < 1 Then
StrPrompt = "How many gadgets are needed - this must be a postive integer"
GoTo redo
End If
MsgBox "User entered " & lngNum
End Sub
In your example 'need' is an integer data type so it will always be an integer.
Have a look at this:
Sub test()
x = Range("A1").Value
If Int(x) / x = 1 Then
MsgBox "Value is an Integer"
Else
MsgBox "Value is not an Integer"
End If
End Sub
or Assuming A1 has the number, put, in any other cell, the formula:
=IF(INT(A1)=A1,"True","False")

Convert string to int if string is a number

I need to convert a string, obtained from excel, in VBA to an interger. To do so I'm using CInt() which works well. However there is a chance that the string could be something other than a number, in this case I need to set the integer to 0. Currently I have:
If oXLSheet2.Cells(4, 6).Value <> "example string" Then
currentLoad = CInt(oXLSheet2.Cells(4, 6).Value)
Else
currentLoad = 0
End If
The problem is that I cannot predict all possible non numeric strings which could be in this cell. Is there a way I can tell it to convert if it's an integer and set to 0 if not?
Use IsNumeric. It returns true if it's a number or false otherwise.
Public Sub NumTest()
On Error GoTo MyErrorHandler
Dim myVar As Variant
myVar = 11.2 'Or whatever
Dim finalNumber As Integer
If IsNumeric(myVar) Then
finalNumber = CInt(myVar)
Else
finalNumber = 0
End If
Exit Sub
MyErrorHandler:
MsgBox "NumTest" & vbCrLf & vbCrLf & "Err = " & Err.Number & _
vbCrLf & "Description: " & Err.Description
End Sub
Cast to long or cast to int, be aware of the following.
These functions are one of the view functions in Excel VBA that are depending on the system regional settings. So if you use a comma in your double like in some countries in Europe, you will experience an error in the US.
E.g., in european excel-version 0,5 will perform well with CDbl(), but in US-version it will result in 5.
So I recommend to use the following alternative:
Public Function CastLong(var As Variant)
' replace , by .
var = Replace(var, ",", ".")
Dim l As Long
On Error Resume Next
l = Round(Val(var))
' if error occurs, l will be 0
CastLong = l
End Function
' similar function for cast-int, you can add minimum and maximum value if you like
' to prevent that value is too high or too low.
Public Function CastInt(var As Variant)
' replace , by .
var = Replace(var, ",", ".")
Dim i As Integer
On Error Resume Next
i = Round(Val(var))
' if error occurs, i will be 0
CastInt = i
End Function
Of course you can also think of cases where people use commas and dots, e.g., three-thousand as 3,000.00. If you require functionality for these kind of cases, then you have to check for another solution.
Try this:
currentLoad = ConvertToLongInteger(oXLSheet2.Cells(4, 6).Value)
with this function:
Function ConvertToLongInteger(ByVal stValue As String) As Long
On Error GoTo ConversionFailureHandler
ConvertToLongInteger = CLng(stValue) 'TRY to convert to an Integer value
Exit Function 'If we reach this point, then we succeeded so exit
ConversionFailureHandler:
'IF we've reached this point, then we did not succeed in conversion
'If the error is type-mismatch, clear the error and return numeric 0 from the function
'Otherwise, disable the error handler, and re-run the code to allow the system to
'display the error
If Err.Number = 13 Then 'error # 13 is Type mismatch
Err.Clear
ConvertToLongInteger = 0
Exit Function
Else
On Error GoTo 0
Resume
End If
End Function
I chose Long (Integer) instead of simply Integer because the min/max size of an Integer in VBA is crummy (min: -32768, max:+32767). It's common to have an integer outside of that range in spreadsheet operations.
The above code can be modified to handle conversion from string to-Integers, to-Currency (using CCur() ), to-Decimal (using CDec() ), to-Double (using CDbl() ), etc. Just replace the conversion function itself (CLng). Change the function return type, and rename all occurrences of the function variable to make everything consistent.
Just use Val():
currentLoad = Int(Val([f4]))
Now currentLoad has a integer value, zero if [f4] is not numeric.
To put it on one line:
currentLoad = IIf(IsNumeric(oXLSheet2.Cells(4, 6).Value), CInt(oXLSheet2.Cells(4, 6).Value), 0)
Here are a three functions that might be useful. First checks the string for a proper numeric format, second and third function converts a string to Long or Double.
Function IsValidNumericEntry(MyString As String) As Boolean
'********************************************************************************
'This function checks the string entry to make sure that valid digits are in the string.
'It checks to make sure the + and - are the first character if entered and no duplicates.
'Valid charcters are 0 - 9, + - and the .
'********************************************************************************
Dim ValidEntry As Boolean
Dim CharCode As Integer
Dim ValidDigit As Boolean
Dim ValidPlus As Boolean
Dim ValidMinus As Boolean
Dim ValidDecimal As Boolean
Dim ErrMsg As String
ValidDigit = False
ValidPlus = False
ValidMinus = False
ValidDecimal = False
ValidEntry = True
For x = 1 To Len(MyString)
CharCode = Asc(Mid(MyString, x, 1))
Select Case CharCode
Case 48 To 57 ' Digits 0 - 9
ValidDigit = True
Case 43 ' Plus sign
If ValidPlus Then 'One has already been detected and this is a duplicate
ErrMsg = "Invalid entry....too many plus signs!"
ValidEntry = False
Exit For
ElseIf x = 1 Then 'if in the first positon it is valide
ValidPlus = True
Else 'Not in first position and it is invalid
ErrMsg = "Invalide entry....Plus sign not in the correct position! "
ValidEntry = False
Exit For
End If
Case 45 ' Minus sign
If ValidMinus Then 'One has already been detected and this is a duplicate
ErrMsg = "Invalide entry....too many minus signs! "
ValidEntry = False
Exit For
ElseIf x = 1 Then 'if in the first position it is valid
ValidMinus = True
Else 'Not in first position and it is invalid
ErrMsg = "Invalide entry....Minus sign not in the correct position! "
ValidEntry = False
Exit For
End If
Case 46 ' Period
If ValidDecimal Then 'One has already been detected and this is a duplicate
ErrMsg = "Invalide entry....too many decimals!"
ValidEntry = False
Exit For
Else
ValidDecimal = True
End If
Case Else
ErrMsg = "Invalid numerical entry....Only digits 0-9 and the . + - characters are valid!"
ValidEntry = False
Exit For
End Select
Next
If ValidEntry And ValidDigit Then
IsValidNumericEntry = True
Else
If ValidDigit = False Then
ErrMsg = "Text string contains an invalid numeric format." & vbCrLf _
& "Use only one of the following formats!" & vbCrLf _
& "(+dd.dd -dd.dd +dd -dd dd.d or dd)! "
End If
MsgBox (ErrMsg & vbCrLf & vbCrLf & "You Entered: " & MyString)
IsValidNumericEntry = False
End If
End Function
Function ConvertToLong(stringVal As String) As Long
'Assumes the user has verified the string contains a valide numeric entry.
'User should call the function IsValidNumericEntry first especially after any user input
'to verify that the user has entered a proper number.
ConvertToLong = CLng(stringVal)
End Function
Function ConvertToDouble(stringVal As String) As Double
'Assumes the user has verified the string contains a valide numeric entry.
'User should call the function IsValidNumericEntry first especially after any user input
'to verify that the user has entered a proper number.
ConvertToDouble = CDbl(stringVal)
End Function

Resources