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.
Related
I am trying to save the ticker symbol I derived using a Rich data type (I am not sure if I said it correctly) from B2 to a variable.
I used this code
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Ticker As String
If Target.Address = "$A$2" Then
Ticker = Activesheet.Range("B2").Value
MsgBox Ticker
End If
End Sub
It happens when I make changes to A2.
I tried to convert it into text or string and it does not work. I assigned the variable as a Variant and String, but still, it won't work. Error is saying "Run time error'13' Type mismatch. But I cannot figure out where I did wrong.
When I debug it, Ticker = ""
I googled around and I cannot find an answer. Is it possible though?
Problem: when you change the rich data type in A2, Excel starts retrieving the ticker symbol in B2 via =A2.[Ticker symbol]. Fetching such data might take a short while, and while the request is pending, B2 will show as #FIELD!. While in this state, B2.Value will be read as an error, so that your code will bounce on Ticker = ActiveSheet.Range("B2").Value, since the variable is dimmed as String. This is why you get the "Type mismatch" error.
Solution: Build a slight delay into your code that will give Excel sufficient time to retrieve the requested data. We can use a Do While Loop for this. Define 2 booleans:
First boolean checks IsError(ActiveSheet.Range("B2"))
Second boolean functions as a timer
Exit the loop when either of these booleans becomes False. You want to include the second boolean to ensure that you do not end up in an infinite loop. This might otherwise happen if the value in B2 will always remain #FIELD, because you didn't supply a valid entry in A2.
Something like the following should work:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Ticker As String
Dim fieldError As Boolean, onTime As Boolean
Dim endTime As Date
'using Target.Text <> "" rather than Target.Value <> "", since the latter will throw an error
'if the range contains a 'rich data type'
If Not Intersect(Target, ActiveSheet.Range("A2")) Is Nothing And Target.Text <> "" Then
'set timeframe to wait for response, set to 1 second here
'we want to check: if "#FIELD!" hasn't changed into a proper value after 1 second
'then data in A2 is probably nonsense: exit loop
endTime = Now + TimeValue("00:00:01")
'set Booleans
onTime = True
fieldError = True
'enter Do While Loop
Do While fieldError And onTime 'continue until
If IsError(ActiveSheet.Range("B2")) Then
Else
'B2 (no longer) an error, switch bool to False to exit loop
fieldError = False
End If
'bool to False after time out to exit loop
onTime = Now < endTime
Loop
'In case of Error, probably incorrect input in A2
On Error GoTo ErrorHandling
Ticker = ActiveSheet.Range("B2").Value
ErrorHandling:
Debug.Print "A2.Text = " & ActiveSheet.Range("A2").Text & "), " & _
"ticker = " & Ticker & ", fieldError = " & fieldError & ", onTime = " & onTime
If Err.Number <> 0 Then
'do stuff (you probably got a correct response, but it's still an error
Debug.Print "Entered ErrorHandling"
Err.Number = 0
End If
Debug.Print "------"
Else
End If
End Sub
To exemplify, below you see a loop in action that populates A2 with a reference to items in the range LIST successively and tries to retrieve the ticker. Notice the output in the immediate window: for "AA", "AAPL" and "AMZN" we are exiting the loop on fieldError = False (ticker found). For "NONSENSE" we are exiting on onTime = False (no ticker: we timed out).
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.
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."
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")
I am creating the validation dynamically and have hit a 256 character limit. My validation looks something like this:
Level 1, Level 2, Level 3, Level 4.....
Is there any way to get around the character limit other then pointing at a range?
The validation is already being produced in VBA. Increasing the limit is the easiest way to avoid any impact on how the sheet currently works.
I'm pretty sure there is no way around the 256 character limit, Joel Spolsky explains why here: http://www.joelonsoftware.com/printerFriendly/articles/fog0000000319.html.
You could however use VBA to get close to replicating the functionality of the built in validation by coding the Worksheet_Change event. Here's a mock up to give you the idea. You will probably want to refactor it to cache the ValidValues, handle changes to ranges of cells, etc...
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ValidationRange As Excel.Range
Dim ValidValues(1 To 100) As String
Dim Index As Integer
Dim Valid As Boolean
Dim Msg As String
Dim WhatToDo As VbMsgBoxResult
'Initialise ValidationRange
Set ValidationRange = Sheet1.Range("A:A")
' Check if change is in a cell we need to validate
If Not Intersect(Target, ValidationRange) Is Nothing Then
' Populate ValidValues array
For Index = 1 To 100
ValidValues(Index) = "Level " & Index
Next
' do the validation, permit blank values
If IsEmpty(Target) Then
Valid = True
Else
Valid = False
For Index = 1 To 100
If Target.Value = ValidValues(Index) Then
' found match to valid value
Valid = True
Exit For
End If
Next
End If
If Not Valid Then
Target.Select
' tell user value isn't valid
Msg = _
"The value you entered is not valid" & vbCrLf & vbCrLf & _
"A user has restricted values that can be entered into this cell."
WhatToDo = MsgBox(Msg, vbRetryCancel + vbCritical, "Microsoft Excel")
Target.Value = ""
If WhatToDo = vbRetry Then
Application.SendKeys "{F2}"
End If
End If
End If
End Sub