Select to view content in your preferred language

VB.Net Restricting TextBox to Positive Long or Double

13633
29
08-28-2012 03:24 PM
RichardFairhurst
MVP Alum
I have been searching the internet for ideas on how to restrict a VB.Net System.Windows.Forms.TextBox control to only accept positive Long or positive Double numeric values.  I think I have come up with a good control set up, primarily based on this post on msdn.  The code within the KeyPress and Leave events below is generic and can be pasted into any Textbox KeyPress and Leave events to produce the behaviors described below.  I am posting this in case it helps others and also to see if anyone has a more elegant way of doing this.

For this example, the behavior criteria of my Long Textbox are as follows:

1.  When the user finishes editing in the Textbox the value of the Textbox must be a Long interger that is positive and greater than or equal to 1 (in this example the Textbox represents an annual average daily trip count which cannot be negative and which is used as a divisor in an equation, so at least 1 annual average daily trip must occur on the road for a valid analysis).

2.  From the keyboard the user must only be able to type numbers.

3.  The user may paste text into the Textbox.  This introduces the possibility for insertion of negative numbers, floating values, and invalid entries which must be resolved in the following ways:
a.  If a negative number is pasted into the Textbox, it must be reset to its absolute value.
b.  If a floating number is pasted into the Textbox, it must be rounded to a whole interger.
c.  If the user pastes invalid text into the Textbox and does not correct an invalid entry prior to leaving the Textbox, the user will be permitted to exit the textbox, but the invalid value will be replaced by the minimum default value (in this case 1).

4.  If a user attempts to leave the Textbox with a blank entry or an entry of 0, the user is permitted to leave but the textbox will be completed using the minimum default value (in this case 1).

5.  Numbers must always be corrected to a conventional format when the user leaves the Textbox.  In other words, a user may enter a value of 023 into the Textbox, but when he exits the Textbox it must appear as 23.

Here is the code that implements this behavior.  The Textbox in this specific example is named txtAADT; however, the code written within the KeyPress and Leave Subs is generic and can be pasted into these two events for any other Textbox to implement the same behavior.
Imports System.Math

  Private Sub txtAADT_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtAADT.KeyPress
    # If the keystroke is not a numeric digit, the keystroke will be disregarded.  Code works for any Textbox KeyPress event.
    If Not (Char.IsDigit(e.KeyChar) Or Char.IsControl(e.KeyChar)) Then
      e.Handled = True
    End If
  End Sub

  Private Sub txtAADT_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtAADT.Leave
    # User has completed their entry and is leaving the Textbox.

    # Create a Textbox variable to make the code below generic so that it can be pasted into any Textbox Leave event
    Dim tbx As System.Windows.Forms.TextBox = sender

    Dim value As Double
    If Not Double.TryParse(tbx.Text, value) Then
      # The user did not correct an invalid entry so replace it with 1
      tbx.Text = 1
    ElseIf value < -1 Then
      # The user pasted a negative value, so make sure it is rounded and take its absolute value
      tbx.Text = Abs(CLng(value))
    ElseIf value < 1 Then
      # The user has entered 0 or pasted in a value between -1 and 1, so replace with minimum value of 1
      tbx.Text = 1
    ElseIf value = vbNull Then
      # The user has blanked out the Textbox, so fill in the Textbox with the minimum value of 1
      tbx.Text = 1
    Else
      # The user has entered a valid postive number, so make sure it is rounded and appears as a conventional number
      tbx.Text = CLng(value)
    End If
  End Sub


The positive Double Textbox has similar requirements to the positive Long Textbox, but it allows an optional single decimal point to be inserted to create non-integer values.  (The code is not designed at this time to respond to local Cultural settings for the decimal character, so that would be a nice enhancement if anyone cares to suggest the code required to do that.)  The following additional or different behaviors are implement with the positive Double Textbox:

1.  When the user finishes editing in the Textbox the value of the Textbox must be a Double value that is positive and greater than or equal to 0 (in this example the Textbox represents a Distance of offset from an intersection which cannot be negative, but which may be 0 where no offset is desired).

2.  From the keyboard the user must only be able to type numbers and at most one decimal character.

3.  The user may paste text into the Textbox.  This introduces the possibility for insertion of negative numbers and invalid entries which must be resolved in the following ways:
a.  If a negative number is pasted into the Textbox, it must be reset to its absolute value.
b.  If the user pastes invalid text into the Textbox and does not correct an invalid entry prior to leaving the Textbox, the user will be permitted to exit the textbox, but the invalid value will be replaced by the minimum default value (in this case 0).

4.  If a user attempts to leave the Textbox with a blank entry, the user is permitted to leave but the textbox will be completed using the minimum default value (in this case 0).

5  A maximum of 4 fractional digits are permitted.  Any fractional digits beyond that must be rounded.  (This is due to the maximum resolution of the data)

6.  Numbers must always be corrected to a conventional format when the user leaves the Textbox.  In other words, a user may enter a value of 023.123456 into the Textbox, but when he exits the Textbox it must appear as 23.1235.

Here is the code for the positive Double Textbox.  The Textbox in this specific example is named txtDistance1; however, the code written within the KeyPress and Leave Subs is generic and can be pasted into these two events for any other Textbox to implement the same behavior.
Imports System.Math

  Private Sub txtDistance1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtDistance1.KeyPress
    # Create a Textbox variable so that the code within this KeyPress event is generic for all positive Double Textboxes.
    Dim tbx As System.Windows.Forms.TextBox = sender
    If Not (Char.IsDigit(e.KeyChar) Or Char.IsControl(e.KeyChar) Or (e.KeyChar = "." And tbx.Text.IndexOf(".") < 0) Or (e.KeyChar = "." And tbx.Text.IndexOf(".") >= 0 And tbx.SelectionLength > 0 And tbx.SelectionStart <= tbx.Text.IndexOf(".") And tbx.SelectionStart + tbx.SelectionLength > tbx.Text.IndexOf("."))) Then
    # Explanation: Prevent the keystroke if it is not a numeric digit, a non-printable character such as a Backspace, or a decimal point where no decimal point exists in the Textbox or where the existing decimal point is contained in a text selection that will be overwritten by the new decimal point.
      e.Handled = True
    End If
  End Sub

  Private Sub txtDistance1_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtDistance1.Leave
    # User has completed their entry and is leaving the Textbox

    # Create a Textbox variable so that the code below is generic and can be pasted into any Textbox Leave event
    Dim tbx As System.Windows.Forms.TextBox = sender
    Dim value As Double
    If Not Double.TryParse(tbx.Text, value) Then
      # Textbox value is invalid, so replace it with the minimum default value (in this case 0)
      tbx.Text = 0
    ElseIf value = vbNull Then
      # The user has blanked out the Textbox, so fill in the Textbox with the minimum value of 0
      tbx.Text = 0
    ElseIf value >= 0 Then
      # value is positive so round to 4 decimal places and correct to conventional format
      tbx.Text = Round(value, 4)
    Else
      # value is negative so round to 4 decimal places, apply the absolute value and correct to conventional format
      tbx.Text = Abs(Round(value, 4))
    End If
  End Sub


Again, I hope this helps.
0 Kudos
29 Replies
LeoDonahue
Deactivated User
I'm not doing a good job of being clear.  Let me try again.

While trying to limit what a user can enter into an input control is fine, you should also validate that input before you use it.

Example:  Esri's map scale tool.  You can type letters in there, but when you hit enter, you'll get a message indicating that what you typed is junk.

[ATTACH=CONFIG]17329[/ATTACH]
0 Kudos
RichardFairhurst
MVP Alum
The problem with putting it into the TextChanged subroutine is that the user can put in invalid characters into the text box, whereas the KeyPress validation won't put those invalid characters there to begin with. Using the code from the MS example, you can still see something like this

[ATTACH=CONFIG]17327[/ATTACH]

It looks like the Masked Textbox requires more user interaction with decimal numbers, requiring them to position the cursor at the correct location to put in a number that has fewer numbers than the maximum number.


I concur on both statements.  The Keypress operation does the best job of ensuring keyboard actions cannot result in unwanted characters and can even restrict certain characters to a certain number of occurrances (as my code has shown) or even to specific positions (were I to use a negative sign I could write a KeyPress routine that ensured the negative sign only occurred once at the beginning of the number and that any attemp to type numbers to the left of it would result in the numbers being placed after the negative sign, without requiring the user to move their cursor to that position).

Restricting cursor movements within the textbox to specific locations that match a mask are unacceptable to me.  Text entry needs to be like normal typing, with all standard editing operations, such as select and overwrite abilities, allowed using intelligent character ordering through the keyboard keypress operation. 

Pasting is the sole path to entering garbage in the Textbox and I have no problem discouraging that practice and penalizing a user for careless use of that data entry method if they do not care to conform to the form's business rules.

When Federal and State Transportation Agencies, State Engineering and Surveying Licensing Boards, and my Department's policy formally adopt standards to accomdate document submissions and field data recordings using hex values and escape codes I will revise my Textbox to accomodate such numbers.  But until then, such entries will be treated as the junk that they are.
0 Kudos
RichardFairhurst
MVP Alum
I'm not doing a good job of being clear.  Let me try again.

While trying to limit what a user can enter into an input control is fine, you should also validate that input before you use it.

Example:  Esri's map scale tool.  You can type letters in there, but when you hit enter, you'll get a message indicating that what you typed is junk.

[ATTACH=CONFIG]17329[/ATTACH]


I concur with this practice of the warning (not the typing of invalid characters from the keyboard), provided that the invalid data would be immediately replaced by valid data and that the warning would only serve to notify the user that:

"You typed - 12.345.678 - which not a valid Base 10 Double Number.  Your invalid entry has been replaced by the default value of 1."

This warning (which is better than a generalized failure notice) would allow the user to recover from their error by recopying their failed entry and pasting it back into the form at which point they could make appropriate corrections according the form's requirements (i.e. 12345.678).

I feel certain that the keyboard for my Textbox is bulletproof (and can be made that way for alternative requirements through the KeyPress method), but I definitely would like to have a more foolproof set of behaviors for the responding to the paste option.  While in my particular case I do not want thousands separators in the Textbox on my form, I would prefer to add a subroutine that would accept pasted numbers with thousands separators as valid numbers and that would reformat them as numbers without thousands separators.

The key to solving this appears to be to find the events that always fire when the paste occurs and when the user is done entering their text by whatever method (perhaps Losing Focus).  I could really use a flowchart outlining the order of more events than just KeyDown, KeyPress, and KeyUp to understand my options.  I will continue looking into the options.
0 Kudos
LeoDonahue
Deactivated User
I don't want to be known for beating a dead horse, but I don't see where you are testing the validity of an integer value.

What if user enters this:  9,223,372,036,854,775,808 (which is larger than the maximum integer size by one) into your integer checking leave event?
0 Kudos
RichardFairhurst
MVP Alum
I don't want to be known for beating a dead horse, but I don't see where you are testing the validity of an integer value.

What if user enters this:  9,223,372,036,854,775,808 (which is larger than the maximum integer size by one) into your integer checking leave event?


Using the keyboard this number could only be entered as: 9223372036854775808 according to my intentional disallowance of the thousands separator.  Assuming the Leave event triggered this value would be replaced by 1 as an invalid value, since it would not parse to a valid Long value, but that is not a problem, because it is far beyond the valid maximum number my form can use.  The maximum number for my form's Long value is well below the one you have suggested, being at most 7 digits long for any real world AADT.  The Leave event already has tests for numbers that are too small (anything below 1 for the Long field example), and adding additional tests for the total number of characters entered for a Long value would be an easy test to add to restrict the number to a valid maximum range.  Exceeding the maximum number would be corrected by replacing the input with the maximum value allowed instead of the minimum value.  I would definitely have other tests specific to my application that would also ensure that the distances entered actually kept the event on my real roads, none of which is anywhere near this long in any standard units of measure I use.  Testing for character length of the Double value on each side of the decimal place would also be an easy test to add for the Double Textbox, and thank you for alerting me to this ommision.

Regardless of where I put it, the code I have used in the Leave event is the minimum validation code I need to have executed, whether it occurs there or in an execute button or whereever.  So I just need to find the best place to put it to make it fire.  My desire is to have it tested as soon as possible upon a user finishing with his entry, which in most cases should be long before the user gets to the point of trying to execute the form if they are filling it in normally (12 completed fields are required for execution in my case).  At this stage I would be inclined to keep the Leave event in my code (with the addition of a maximum value validation) and just add a double check routine to be executed just prior to form execution to ensure that the user did not find a way around triggering the Leave event.
0 Kudos
RichardFairhurst
MVP Alum
Here's a subroutine I found that I usually put in my text box KeyPress functions when I want to restrict what's being typed in. This doesn't account for hex values, but I doubt that the users would even know what they are...

When using this with numbers, I have additional checks to make sure there's not more than one decimal place or negative sign, in addition. Here's an example for a group of textboxes that can be decimal degree or degree minutes seconds or just numbers...


It took me a while to understand how the mask worked, but the second example of the decimal degrees or degrees minutes seconds helped me figure it out.  I assume you have other checks that would deal with minutes and seconds being between 0 and 59 that you did not publish, or did I miss something?

You also designed this with multiple textboxes for the Degrees Minutes Seconds input.  Do you have any idea how it would be written to work as a single Textbox. Say that the user set a checkbox to specify the format mask they were entering so that one Textbox could accept a variety of formats depending on user preference at the time of data entry and that internal code would convert it to match the format specifications of a database field.  (I realize the check box could expose different sets of Textbox fields to handle and preparse each format, which would be a safer design, but I am trying to explore the limits of what a Textbox can do in this discussion thread).
0 Kudos
RichardFairhurst
MVP Alum
Your code is only checking for "leave" events, which you hope the user enters a value and leaves that text box.  What if the user enters junk into a text box and doesn't do anything to trigger the leave event before they submit?


I have not encountered any case where I have been able to execute a button that submits the form's data for execution without first triggering the Leave event of the Textbox (whether or not I changed anything in the Textbox), so unless you can show me a way to submit without triggering the Leave event I don't think your second comment applies.  Therefore I have every reason to believe that the KeyPress and Leave events are all that is required to ensure that a valid entry will be in the Textbox when the user has finished with that field and wants to submit their results for execution.

So at this point I plan on amending my original code to add a check in the Leave event for the allowable maximum value and to provide a notification prior to execution of the form to the user to let him know when his entry was rejected, what the invalid value was that he entered, and that the value was replaced with a default minimum or maximum value so that he is aware of his error and can optionally correct it before proceeding with execution.  I also plan to revise the code to use globalization methods that will use the decimal character that conforms to a users local cultural settings for the benefit of programmers working in other cultures that would want to use my code (although that code may need to have its behavior verified by a programmer in another culture before putting it in use).  Finally, I plan on determining the local setting for the thousands separator and parsing it out of an entry that was pasted into the textbox so that it will be treated as a valid number and accepted in the field (subject to the other checks).
0 Kudos
KenBuja
MVP Esteemed Contributor
It took me a while to understand how the mask worked, but the second example of the decimal degrees or degrees minutes seconds helped me figure it out.  I assume you have other checks that would deal with minutes and seconds being between 0 and 59 that you did not publish, or did I miss something?

You also designed this with multiple textboxes for the Degrees Minutes Seconds input.  Do you have any idea how it would be written to work as a single Textbox. Say that the user set a checkbox to specify the format mask they were entering so that one Textbox could accept a variety of formats depending on user preference at the time of data entry and that internal code would convert it to match the format specifications of a database field.  (I realize the check box could expose different sets of Textbox fields to handle and preparse each format, which would be a safer design, but I am trying to explore the limits of what a Textbox can do in this discussion thread).


In my case, I was using a radio button to allow the user to enter positions by decimal degree or DMS by making one set of text boxes visible and the other invisible.

You could do this by using something like

If chkInteger.Checked Then  
    Mask = "09--"
Else
    Mask = "09--.."
End If


You'd have to incorporate validation in the checkbox in case the user selects that check box after typing in a value.
0 Kudos
LeoDonahue
Deactivated User
I have not encountered any case where I have been able to execute a button that submits the form's data for execution without first triggering the Leave event of the Textbox (whether or not I changed anything in the Textbox), so unless you can show me a way to submit without triggering the Leave event I don't think your second comment applies. 

You would be able to submit a form button without the leave event triggering if your button has a hot key assigned to it, such as the Text property of your button reads: &Submit

That would submit your form with no values, assuming you have a hot key assigned to your button, which you probably don't.  But also means that if you wanted one, you would be forced to validate your input for all text boxes in the event the form is submitted without any values.
0 Kudos
RichardFairhurst
MVP Alum
You would be able to submit a form button without the leave event triggering if your button has a hot key assigned to it, such as the Text property of your button reads: &Submit

That would submit your form with no values, assuming you have a hot key assigned to your button, which you probably don't.  But also means that if you wanted one, you would be forced to validate your input for all text boxes in the event the form is submitted without any values.


Given that validating the input data is the primary function of my form I would not assign a hot key to let the user submit the form without firing the Leave event.  If I did, you are correct that I would have to find another location (the submit button) to do the validation.  Is there a way to detect that a hot key was used?  If so the additional validation would only need to occur if the hot key was triggered.  Probably the best compromise would be to create separate Subs of Functions that placed the validation contents currently within the Leave event outside of that event.  That way the method could be called from both the Leave event and the Submit button.  Then the validation would always occur with whichever event fired earliest, which is my preference.
0 Kudos