Resolved Number format culture

C#Marcos

New member
Joined
Jun 13, 2022
Messages
2
Programming Experience
Beginner
Hey guys, I´m programming a code for my engeneering classes and now I realized I´m having some trouble with the number formats in other languages.
My native language is pt-BR and we use the coma "," for decimals while the english speakers and also the computer language uses the dot "."
So I´ve made a lot of Text Boxes for input values in my application and also fill those Text Boxes with the text "0,0", so people should know what´s the input format the application requires.
At this point everything was okay and my results were all okay as well. Then, a friend of mine, which lives in a spanish language country, had run some tests for me and his results were not okay at all.
So I´ve made some research and found something called System.Globalization, then I tought it could be caused by the comas and dots.
Then, I updated my KeyPress event to accept only dots.

Exemple:
KeyPress Code:
private void tb_bwA_KeyPress(object sender, KeyPressEventArgs e)

        {

            if (!Char.IsDigit(e.KeyChar) && e.KeyChar != (char)8 && e.KeyChar != '.')

            {

                e.Handled = true;

            }

            if (e.KeyChar == '.' && (sender as TextBox).Text.IndexOf('.') > -1)

            {

                e.Handled = true;

            }

        }

TextBox initial text:
tb_bwA.Text = "0,0";

So it seemed to be fixed, once it was working properly in my computer (pt-BR) and in his computer (es-PE). But I forgot to update the TextBox texts and was using (0,0).
When I updated my Text Boxes texts for (0.0) the results while running in my computer gone all wrong.

So my question is: Is this any possibility to change the number format culture at the first time I open my Form to accept only dots as a decimal and work properly no matter where I open my application ?
I can´t find this code nowhere and I´ve been stuck in this problem for 2 days.

Thanks in advance.
 
There's a few things at play here.
First, do not be stringly typed. You should be strongly typed. The value that you really are dealing with are floating point or decimal numbers, and they just happen to have string representations. As you've figured out, different cultures present the same number value different ways with different decimal number separators. So for your initialization of your text box, I suggest using something like:
C#:
const double InitialValue = 0.0;
:
tb_bwA.Text = InitialValue.ToString("F1");  // get back a fixed point decimal number with one decimal digit

Second, avoid using magic numbers. For the people who have not memorized the ASCII chart, they will be wondering what 8 stand for on line 5 of your key press handling code. You should either declare a your own constant, or use the framework's Keys.Back to help readers know the significance of this.

Third, in a similar vein, your hard coded use of '.' signifies what? It will only work for cultures where the period is used as the decimal separator. You should be using CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator or CurrencyDecimalSeparator as appropriate. You were already part way there when you started learning about System.Globalization.

Fourth, I understand the compulsion to try to filter the user inputs as keys are pressed and trying to stop the user from entering invalid input. I used to try to do the same in the past in my DOS programs and early Windows and Mac programs. In the end, it can become a crazy battle against the user. How do will you deal with a user who wants to copy and paste? Now you have to handle Ctrl-V and Ctrl-Insert keypresses. How will you deal with users with disabilities who may use adaptive software which doesn't emulate keypresses, but rather uses the Windows WM_SETTEXT messages to put in values into the textboxes? How will you handle IMEs? How will you handle pen input?

The much better approach is to use the Windows Forms Validation and Error Handling that is built into WinForms.

Or if you don't want to go down that learning curve, just wait for the textbox text changed events and use double.TryParse()[icode] or [icode]decimal.TryParse() to check if the input is valid. If the input is invalid, then beep and mark the field as invalid somehow.


 
Why don't you just respect the number formatting of the current system, whatever that may be? You can parse and format numbers without specifying a culture or specifying the current culture and it will just work, showing every user what they expect to see and allowing every user to enter what they expect to be able to enter. Your event handler can then become:
C#:
var keyChar = e.KeyChar;
var decimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;

e.Handled = char.IsDigit(keyChar) ||
            char.IsControl(keyChar) ||
            (keyChar.ToString() == decimalSeparator && (sender as TextBox).Text.Contains(decimalSeparator));
If you want to display a default value in the TextBox, do this:
C#:
tb_bwA.Text = 0.ToString("N1");
That will display zero with one decimal place, using the appropriate decimal separator for the current system.
 
For the record, here's a custom numeric text box control I created some time ago, which you can use in place of a regular TextBox:
VB.NET:
Imports System.Globalization

Public Class SimpleNumberBox
    Inherits System.Windows.Forms.TextBox

    Private Const WM_PASTE As Integer = &H302

    Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs)
        Dim keyChar = e.KeyChar
        Dim formatInfo = NumberFormatInfo.CurrentInfo

        If Char.IsControl(keyChar) OrElse _
           Char.IsDigit(keyChar) OrElse _
           ((keyChar = formatInfo.NegativeSign OrElse _
             keyChar = formatInfo.NumberDecimalSeparator) AndAlso _
            Me.ValidateText(keyChar)) Then
            MyBase.OnKeyPress(e)
        Else
            e.Handled = True
        End If
    End Sub

    Protected Overrides Sub OnValidated(ByVal e As System.EventArgs)
        If Not Decimal.TryParse(Me.Text, New Decimal) Then
            Me.Clear()
        End If

        MyBase.OnValidated(e)
    End Sub

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If m.Msg <> WM_PASTE OrElse _
           Not Clipboard.ContainsText() OrElse _
           Me.ValidateText(Clipboard.GetText()) Then
            MyBase.WndProc(m)
        End If
    End Sub

    Private Function ValidateText(ByVal newText As String) As Boolean
        Dim isValid = True
        Dim currentText = Me.Text
        Dim selectionStart = Me.SelectionStart
        Dim proposedText = currentText.Substring(0, selectionStart) & _
                           newText & _
                           currentText.Substring(selectionStart + Me.SelectionLength)

        If Not Decimal.TryParse(proposedText, _
                                NumberStyles.AllowLeadingSign Or NumberStyles.AllowDecimalPoint, _
                                Nothing, _
                                New Decimal) Then
            Dim formatInfo = NumberFormatInfo.CurrentInfo
            Dim negativeSign = formatInfo.NegativeSign
            Dim decimalSeparator = formatInfo.NumberDecimalSeparator

            isValid = (proposedText = negativeSign) OrElse _
                      (proposedText = decimalSeparator) OrElse _
                      (proposedText = negativeSign & decimalSeparator)
        End If

        Return isValid
    End Function

End Class
The code is VB.NET but you can build it in a library or translate the code if preferred. Here's an even more rigorous implementation, although there may still be some bugs in this one for edge cases or some cultures:
VB.NET:
Imports System.ComponentModel
Imports System.Globalization
Imports System.Media

''' <summary>
''' A text box control that accepts only numeric input.
''' </summary>
<DefaultProperty("NullableValue"), _
DefaultEvent("NullableValueChanged"), _
ToolboxBitmap(GetType(System.Windows.Forms.TextBox))> _
Public Class NumberBox
    Inherits System.Windows.Forms.TextBox

#Region " Types "

    ''' <summary>
    ''' Defines constants that indicate how blank text is treated when a <see cref="NumberBox" /> loses focus.
    ''' </summary>
    Public Enum BlankModes
        ''' <summary>
        ''' Indicates that blank text is accepted.
        ''' </summary>
        <Description("Accept")> _
        Accept
        ''' <summary>
        ''' Indicates that blank text is converted to zero.
        ''' </summary>
        <Description("Convert to Zero")> _
        ConvertToZero
        ''' <summary>
        ''' Indicates that an exception is thrown.
        ''' </summary>
        <Description("Reject")> _
        Reject
    End Enum

#End Region 'Types

#Region " Constants "

    ''' <summary>
    ''' The message received when the user pastes into the control.
    ''' </summary>
    Private Const WM_PASTE As Integer = &H302

#End Region 'Constants

#Region " Fields "

    ''' <summary>
    ''' Indicates whether a decimal point is permitted.
    ''' </summary>
    Private _allowDecimal As Boolean = True
    ''' <summary>
    ''' Indicates whether the text can contain formatting when the control has focus.
    ''' </summary>
    ''' <remarks>
    ''' This field is relevant when the text is being formatted as the control loses focus.
    ''' </remarks>
    Private allowFormattingWhenFocused As Boolean = False
    ''' <summary>
    ''' Indicates whether a negative sign is permitted.
    ''' </summary>
    Private _allowNegative As Boolean = True
    ''' <summary>
    ''' Indicates whether the control makes a sound when input is rejected.
    ''' </summary>
    Private _beepOnInvalidInput As Boolean = True
    ''' <summary>
    ''' Indicates how blank text is treated.
    ''' </summary>
    Private _blankMode As BlankModes = BlankModes.Accept
    ''' <summary>
    ''' The standard or custom format string used to format the number for display.
    ''' </summary>
    Private _format As String
    ''' <summary>
    ''' Provides formatting information, generally specific to culture.
    ''' </summary>
    Private _formatProvider As NumberFormatInfo = Nothing
    ''' <summary>
    ''' The value contained in the control.
    ''' </summary>
    ''' <remarks>
    ''' Supports null values.
    ''' </remarks>
    Private _nullableValue As Decimal?
    ''' <summary>
    ''' Indicates whether the <see cref="NullableValue" /> property was set to trigger a <see cref="TextChanged" /> event.
    ''' </summary>
    Private nullableValueSet As Boolean = False
    ''' <summary>
    ''' Indicates whether the contents should be reformatted in the <see cref="OnEnter" /> method.
    ''' </summary>
    ''' <remarks>
    ''' The field is set to <b>false</b> when the control fails validation to prevent the contents being redisplayed.
    ''' </remarks>
    Private reformatOnEnter As Boolean = True
    ''' <summary>
    ''' The value contained in the control.
    ''' </summary>
    ''' <remarks>
    ''' Does not support null values.
    ''' </remarks>
    Private _value As Decimal
    ''' <summary>
    ''' Indicates whether the <see cref="Value" /> property was set to trigger a <see cref="TextChanged" /> event.
    ''' </summary>
    Private valueSet As Boolean = False

    ''' <summary>
    ''' All possible formatting patterns for negative numbers.
    ''' </summary>
    ''' <remarks>
    ''' The index of each pattern corresponds to the equivalent value for the <see cref="NumberFormatInfo.NumberNegativePattern" /> property.
    ''' </remarks>
    Private ReadOnly numberNegativePatterns As String() = {"(n)", _
                                                           "-n", _
                                                           "- n", _
                                                           "n-", _
                                                           "n -"}
    ''' <summary>
    ''' All possible formatting patterns for positive currency values.
    ''' </summary>
    ''' <remarks>
    ''' The index of each pattern corresponds to the equivalent value for the <see cref="NumberFormatInfo.CurrencyPositivePattern" /> property.
    ''' </remarks>
    Private ReadOnly currencyPositivePatterns As String() = {"$n", _
                                                             "n$", _
                                                             "$ n", _
                                                             "n $"}
    ''' <summary>
    ''' All possible formatting patterns for negative currency values.
    ''' </summary>
    ''' <remarks>
    ''' The index of each pattern corresponds to the equivalent value for the <see cref="NumberFormatInfo.CurrencyNegativePattern" /> property.
    ''' </remarks>
    Private ReadOnly currencyNegativePatterns As String() = {"($n)", _
                                                             "-$n", _
                                                             "$-n", _
                                                             "$n-", _
                                                             "(n$)", _
                                                             "-n$", _
                                                             "n-$", _
                                                             "n$-", _
                                                             "-n $", _
                                                             "-$ n", _
                                                             "n $-", _
                                                             "$ n-", _
                                                             "$ -n", _
                                                             "n- $", _
                                                             "($ n)", _
                                                             "(n $)"}

#End Region 'Fields

#Region " Properties "

    ''' <summary>
    ''' Gets or sets a value indicating whether the control accepts a decimal point.
    ''' The default is <b>true</b>.
    ''' </summary>
    ''' <value>
    ''' <b>true</b> if the control accepts a decimal point; otherwise <b>false</b>.
    ''' </value>
    <Category("Behavior"), _
    Description("Indicates whether a decimal point is permitted."), _
    DefaultValue(True)> _
    Public Property AllowDecimal() As Boolean
        Get
            Return Me._allowDecimal
        End Get
        Set(ByVal value As Boolean)
            If Me._allowDecimal <> value Then
                Me._allowDecimal = value

                'Raise the AllowDecimalChanged event.
                Me.OnAllowDecimalChanged(EventArgs.Empty)

                If Not Me._allowDecimal AndAlso Me.NullableValue.HasValue Then
                    'Remove the decimal portion of the number if there is one.
                    Me.Value = Decimal.Truncate(Me.Value)
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value indicating whether the control accepts a negative sign.
    ''' The default is <b>true</b>.
    ''' </summary>
    ''' <value>
    ''' <b>true</b> if the control accepts a negative sign; otherwise <b>false</b>.
    ''' </value>
    <Category("Behavior"), _
    Description("Indicates whether a negative sign is permitted."), _
    DefaultValue(True)> _
    Public Property AllowNegative() As Boolean
        Get
            Return Me._allowNegative
        End Get
        Set(ByVal value As Boolean)
            If Me._allowNegative <> value Then
                Me._allowNegative = value

                'Raise the AllowNegativeChanged event.
                Me.OnAllowNegativeChanged(EventArgs.Empty)

                If Not Me._allowNegative AndAlso _
                   Me.NullableValue.HasValue Then
                    'Remove the negative sign from the number if there is one.
                    Me.Value = Math.Abs(Me.Value)
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value indicating whether a sound is played when invalid input is rejected.
    ''' </summary>
    ''' <value>
    ''' <b>true</b> if a sound is played when input is rejected; otherwise, <b>false</b>.
    ''' </value>
    ''' <remarks>
    ''' The sound played is <see cref="SystemSounds.Beep">Beep</see>.
    ''' </remarks>
    <Category("Behavior"), _
    Description("Indicates whether the control makes a sound when input is rejected."), _
    DefaultValue(True)> _
    Public Property BeepOnInvalidInput() As Boolean
        Get
            Return Me._beepOnInvalidInput
        End Get
        Set(ByVal value As Boolean)
            If Me._beepOnInvalidInput <> value Then
                Me._beepOnInvalidInput = value

                'Raise the BeepOnInvalidInputChanged event.
                Me.OnBeepOnInvalidInputChanged(EventArgs.Empty)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value indicating how blank text is treated when the form loses focus.
    ''' The default is <see cref="BlankModes.Accept">Accept</see>.
    ''' </summary>
    ''' <value>
    ''' One of the <see cref="BlankModes" /> values.
    ''' </value>
    ''' <remarks>
    ''' If this property is set to <see cref="BlankModes.Reject">Reject</see> and the control is blank when the user tries to close the form, the form will not close.
    ''' In this case, it is recommended to handle the <see cref="Form.FormClosing">FormClosing</see> event of the form and explicitly set <see cref="FormClosingEventArgs.Cancel">Cancel</see> to <b>false</b>.
    ''' Doing so will allow the form to close without the user having to enter a value that won't be used.
    ''' </remarks>
    <Category("Behavior"), _
    Description("Indicates how blank text is treated."), _
    DefaultValue(BlankModes.Accept)> _
    Public Property BlankMode() As BlankModes
        Get
            Return Me._blankMode
        End Get
        Set(ByVal value As BlankModes)
            If Me._blankMode <> value Then
                Me._blankMode = value

                'Raise the BlankModeChanged event.
                Me.OnBlankModeChanged(EventArgs.Empty)

                If Me.Text = String.Empty AndAlso _
                   Not Me.Focused AndAlso _
                   value <> BlankModes.Accept Then
                    'Convert blank text to zero.
                    Me.NullableValue = Decimal.Zero
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value used to format the number for display.
    ''' </summary>
    ''' <value>
    ''' A valid standard or custom numeric format string.
    ''' </value>
    ''' <remarks>
    ''' Formatting is only applied when the control does not have focus.
    ''' </remarks>
    <Category("Appearance"), _
    Description("The standard or custom format string used to format the number in the control."), _
    DefaultValue("")> _
    Public Property Format() As String
        Get
            Return Me._format
        End Get
        Set(ByVal value As String)
            If Me._format <> value Then
                Me._format = value

                'Raise the FormatChanged event.
                Me.OnFormatChanged(EventArgs.Empty)

                'Update the display.
                Me.DisplayNumber(Me.NullableValue, Not Me.Focused)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets an object that provides formatting information.
    ''' </summary>
    ''' <value>
    ''' A <see cref="NumberFormatInfo" /> object or a null reference to use number formatting from the current culture.
    ''' </value>
    <Browsable(False)> _
    Public Property FormatProvider() As NumberFormatInfo
        Get
            Return Me._formatProvider
        End Get
        Set(ByVal value As NumberFormatInfo)
            If Me.FormatProvider IsNot value Then
                Me._formatProvider = value

                'Raise the FormatProviderChanged event.
                Me.OnFormatProviderChanged(EventArgs.Empty)

                'Update the display.
                Me.DisplayNumber(Me.NullableValue, Not Me.Focused)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the numeric value stored in the control.
    ''' </summary>
    ''' <value>
    ''' A null reference if the control is blank; otherwise, a <see cref="Decimal" /> value.
    ''' </value>
    <Category("Appearance"), _
    Description("The value contained in the control, with support for null values.")> _
    Public Property NullableValue() As Decimal?
        Get
            Return Me._nullableValue
        End Get
        Set(ByVal value As Decimal?)
            'Reject invalid data.
            If Not Me.ValidateNumber(value) Then
                Throw New ArgumentException(String.Format("'{0}' is not a valid value for NullableValue.", _
                                                          value))
            End If

            If Not Me._nullableValue.Equals(value) Then
                'The NullableValueChanged event will be raised when the Text changes.
                'This ensures it is raised before ValueChanged and TextChanged.
                Me.nullableValueSet = True

                'Update the display.
                Me.DisplayNumber(value, Not Me.Focused)
            End If
        End Set
    End Property

    ''' <summary>
    ''' <para>
    ''' This API supports the control's infrastructure and is not intended to be used directly from your code.
    ''' </para>
    ''' <para>
    ''' Gets or sets the text currently displayed in the <see cref="NumberBox" />.
    ''' </para>
    ''' </summary>
    ''' <value>
    ''' The text displayed in the control.
    ''' </value>
    ''' <remarks>
    ''' The data in the control should be accessed using the <see cref="Value" /> or <see cref="NullableValue" /> property.
    ''' </remarks>
    <Browsable(False), _
    EditorBrowsable(EditorBrowsableState.Never), _
    Bindable(False)> _
    Public Overrides Property Text() As String
        Get
            Return MyBase.Text
        End Get
        Set(ByVal value As String)
            MyBase.Text = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the numeric value stored in the control.
    ''' </summary>
    ''' <value>
    ''' A numeric representation of the text currently displayed in the control.  Blank text corresponds to a value of <see cref="Decimal.Zero">Zero</see>.
    ''' </value>
    <Browsable(False)> _
    Public Property Value() As Decimal
        Get
            Return Me._value
        End Get
        Set(ByVal value As Decimal)
            'Reject invalid data.
            If Not Me.ValidateNumber(value) Then
                Throw New ArgumentException(String.Format("'{0}' is not a valid value for Value.", _
                                                          value))
            End If

            If Not Me._value = value Then
                'The ValueChanged event will be raised when the Text changes.
                'This ensures it is raised before NullableValueChanged and TextChanged.
                Me.valueSet = True

                'Update the display.
                Me.DisplayNumber(value, Not Me.Focused)
            End If
        End Set
    End Property

#End Region 'Properties

#Region " Events "

    ''' <summary>
    ''' Occurs when the <see cref="AllowDecimal" /> property value changes.
    ''' </summary>
    <Category("Behavior"), _
    Description("Occurs when the AllowDecimal property value changes.")> _
    Public Event AllowDecimalChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="AllowNegative" /> property value changes.
    ''' </summary>
    <Category("Behavior"), _
    Description("Occurs when the AllowNegative property value changes.")> _
    Public Event AllowNegativeChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="BeepOnInvalidInput" /> property value changes.
    ''' </summary>
    <Category("Behavior"), _
    Description("Occurs when the BeepOnInvalidInput property value changes.")> _
    Public Event BeepOnInvalidInputChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="BlankMode" /> property value changes.
    ''' </summary>
    <Category("Behavior"), _
    Description("Occurs when the BlankMode property value changes.")> _
    Public Event BlankModeChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="Format" /> property value changes.
    ''' </summary>
    <Category("Property Changed"), _
    Description("Occurs when the Format property value changes.")> _
    Public Event FormatChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="FormatProvider" /> property value changes.
    ''' </summary>
    <Category("Property Changed"), _
    Description("Occurs when the FormatProvider property value changes.")> _
    Public Event FormatProviderChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="NullableValue" /> property value changes.
    ''' </summary>
    <Category("Property Changed"), _
    Description("Occurs when the NullableValue property value changes.")> _
    Public Event NullableValueChanged As EventHandler
    ''' <summary>
    ''' Occurs when the <see cref="Value" /> property value changes.
    ''' </summary>
    <Category("Property Changed"), _
    Description("Occurs when the Value property value changes.")> _
    Public Event ValueChanged As EventHandler

#End Region 'Events

#Region " Methods "

#Region " Protected Methods "

    ''' <summary>
    ''' Raises the <see cref="AllowDecimalChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnAllowDecimalChanged(ByVal e As EventArgs)
        RaiseEvent AllowDecimalChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="AllowNegativeChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnAllowNegativeChanged(ByVal e As EventArgs)
        RaiseEvent AllowNegativeChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="BeepOnInvalidInputChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnBeepOnInvalidInputChanged(ByVal e As EventArgs)
        RaiseEvent BeepOnInvalidInputChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="BlankModeChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnBlankModeChanged(ByVal e As EventArgs)
        RaiseEvent BlankModeChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="FormatChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnFormatChanged(ByVal e As EventArgs)
        RaiseEvent FormatChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="FormatProviderChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnFormatProviderChanged(ByVal e As EventArgs)
        RaiseEvent FormatProviderChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="NullableValueChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnNullableValueChanged(ByVal e As EventArgs)
        RaiseEvent NullableValueChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="ValueChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overridable Sub OnValueChanged(ByVal e As EventArgs)
        RaiseEvent ValueChanged(Me, e)
    End Sub


    ''' <summary>
    ''' Removes formatting from the text when the control receives focus.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)
        'reformatOnEnter is True by default and set to False when the control fails validation.
        'In that case, the OnEnter method is invoked but the contents should not be reformatted.
        If Me.reformatOnEnter Then
            'Strip formatting from the text.
            Me.DisplayNumber(Me.NullableValue, False)
        Else
            'Set reformatOnEnter back to its default value.
            Me.reformatOnEnter = True
        End If

        MyBase.OnEnter(e)
    End Sub

    ''' <summary>
    ''' Validates input as it's entered into the control.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs)
        Dim keyChar As Char = e.KeyChar

        'All control characters are allowed.
        If Not Char.IsControl(keyChar) Then
            'Block the input if it would create invalid text.
            e.Handled = Not Me.ValidatePartialText(keyChar)

            If e.Handled Then
                Me.PlayInvalidInputSound()
            End If
        End If

        MyBase.OnKeyPress(e)
    End Sub

    ''' <summary>
    ''' Raises the <see cref="TextChanged" /> event.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    ''' <remarks>
    ''' Sets the <see cref="NullableValue" /> and <see cref="Value" /> properties when the <see cref="Text" /> changes.
    ''' </remarks>
    Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
        'Remember the old numeric values.
        Dim oldNullableValue = Me._nullableValue
        Dim oldValue = Me._value

        'Set the new numeric values.  The fields are used rather than the properties so that the Text won't be changed again.
        If Me.TextLength = 0 AndAlso _
           (Me.Focused OrElse Me.BlankMode = BlankModes.Accept) Then
            'The control is blank.
            Me._nullableValue = Nothing
            Me._value = Decimal.Zero
        Else
            Dim number As Decimal

            'Get the number the text represents or zero if it doesn't represent a number.
            Decimal.TryParse(Me.Text, _
                             Me.GetNumberStyles(True, True), _
                             Me.FormatProvider, _
                             number)
            Me._value = number
            Me._nullableValue = number
        End If

        'Raise the appropriate events in the appropriate order based on the property that was initially set.
        If Me.nullableValueSet Then
            'Setting the NullableValue property prompted the change.
            Me.OnNullableValueChanged(EventArgs.Empty)

            If Me._value <> oldValue Then
                Me.OnValueChanged(EventArgs.Empty)
            End If

            MyBase.OnTextChanged(e)
            Me.nullableValueSet = False
        ElseIf Me.valueSet Then
            'Setting the Value property prompted the change.
            Me.OnValueChanged(EventArgs.Empty)
            Me.OnNullableValueChanged(EventArgs.Empty)
            MyBase.OnTextChanged(e)
            Me.valueSet = False
        Else
            'Setting the Text property prompted the change.
            MyBase.OnTextChanged(e)

            If Not Me._nullableValue.Equals(oldNullableValue) Then
                Me.OnNullableValueChanged(EventArgs.Empty)
            End If

            If Me._value <> oldValue Then
                Me.OnValueChanged(EventArgs.Empty)
            End If
        End If
    End Sub

    ''' <summary>
    ''' Applies the appropriate formatting to the number when the control is losing focus.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    Protected Overrides Sub OnValidated(ByVal e As System.EventArgs)
        'This is required to allow formatting to be applied while the control still has focus.
        Me.allowFormattingWhenFocused = True

        'Add formatting to the text when the control has passed validation.
        Me.DisplayNumber(Me.NullableValue, True)

        MyBase.OnValidated(e)

        Me.allowFormattingWhenFocused = False
    End Sub

    ''' <summary>
    ''' Validates the contents of the control and prevents it losing focus if it is invalid.
    ''' </summary>
    ''' <param name="e">
    ''' The data for the event.
    ''' </param>
    ''' <remarks>
    ''' The control will not lose focus if it is blank and <see cref="BlankMode" /> is set to <see cref="BlankModes.Reject">Reject</see>.
    ''' </remarks>
    Protected Overrides Sub OnValidating(ByVal e As System.ComponentModel.CancelEventArgs)
        MyBase.OnValidating(e)

        If Not e.Cancel Then
            'Don't let the control lose focus if it is blank and blank input is not allowed.
            e.Cancel = (Me.TextLength = 0 AndAlso Me.BlankMode = BlankModes.Reject)

            If e.Cancel Then
                Me.PlayInvalidInputSound()

                'Don't reformat the contents after a failed validation.
                Me.reformatOnEnter = False
            End If
        End If
    End Sub

    ''' <summary>
    ''' Processes messages received from the operating system.
    ''' </summary>
    ''' <param name="m">
    ''' The message from the operating system.
    ''' </param>
    ''' <remarks>
    ''' WM_PASTE messages are filtered out if pasting would result in invalid data.
    ''' </remarks>
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        'Intercept WM_PASTE messages and paste manually.
        If m.Msg = WM_PASTE AndAlso Clipboard.ContainsText() Then
            If Me.ValidatePastedText() Then
                Me.PasteClipboardText()
            Else
                Me.PlayInvalidInputSound()
            End If
        Else
            MyBase.WndProc(m)
        End If
    End Sub

#End Region 'Protected Methods

#Region " Private Methods "

    ''' <summary>
    ''' Displays a number using the appropriate formatting.
    ''' </summary>
    ''' <param name="number">
    ''' The number to display.
    ''' </param>
    ''' <param name="applyFormatting">
    ''' Indicates whether formatting should be applied.
    ''' </param>
    Private Sub DisplayNumber(ByVal number As Decimal?, ByVal applyFormatting As Boolean)
        If Not number.HasValue AndAlso Me.BlankMode = BlankModes.Accept Then
            'There is no number to display.
            Me.Text = String.Empty
        ElseIf number.HasValue OrElse Me.BlankMode <> BlankModes.Reject Then
            'Display the value if there is one or zero for a blank value.
            Dim numberToDisplay As Decimal = If(number, Decimal.Zero)

            If Not Me.AllowDecimal Then
                'Remove the decimal portion if there is one.
                numberToDisplay = Decimal.Truncate(numberToDisplay)
            End If

            If Not Me.AllowNegative Then
                'Remove the negative sign if there is one.
                numberToDisplay = Math.Abs(numberToDisplay)
            End If

            Dim format As String = If(applyFormatting, _
                                      Me.Format, _
                                      Nothing)

            Me.Text = numberToDisplay.ToString(format, Me.FormatProvider)
        End If
    End Sub

    ''' <summary>
    ''' Displays text with appropriate formatting, applying or stripping as required.
    ''' </summary>
    ''' <param name="text">
    ''' The text to be displayed.
    ''' </param>
    Private Sub DisplayText(ByVal text As String, ByVal applyFormatting As Boolean)
        Dim number As Decimal? = Nothing

        If Not String.IsNullOrEmpty(text) Then
            Dim value As Decimal

            'Convert the current text to a number.
            Decimal.TryParse(text, _
                             Me.GetNumberStyles(True, True), _
                             Me.FormatProvider, _
                             value)

            number = value
        End If

        Me.DisplayNumber(number, applyFormatting)
    End Sub

    ''' <summary>
    ''' Gets the formatting pattern that should be used to display a currency value.
    ''' </summary>
    ''' <param name="number">
    ''' The number to be formatted.
    ''' </param>
    ''' <returns>
    ''' A <b>string</b> containing a formatting pattern.
    ''' </returns>
    ''' <remarks>
    ''' The value returned is not a format string, but rather the pattern that would be
    ''' produced if <i>number</i> was formatted using the standard currency format string, i.e. "c".
    ''' </remarks>
    Private Function GetCurrencyPattern(ByVal number As Decimal) As String
        Dim formatProvider As NumberFormatInfo = Me.GetFormatProvider()
        Dim currencyPattern As String

        If number < Decimal.Zero Then
            currencyPattern = Me.currencyNegativePatterns(formatProvider.CurrencyNegativePattern)
        Else
            currencyPattern = Me.currencyPositivePatterns(formatProvider.CurrencyPositivePattern)
        End If

        Return currencyPattern
    End Function

    ''' <summary>
    ''' Gets the current numeric format provider.
    ''' </summary>
    ''' <returns>
    ''' The value of the <see cref="FormatProvider" /> if it has been set; otherwise, the <see cref="NumberFormatInfo" /> for the current culture.
    ''' </returns>
    Private Function GetFormatProvider() As NumberFormatInfo
        Return If(Me.FormatProvider, NumberFormatInfo.CurrentInfo)
    End Function

    ''' <summary>
    ''' Gets the allowed number styles based on the current state of the control.
    ''' </summary>
    ''' <returns>
    ''' A composite <see cref="NumberStyles" /> value.
    ''' </returns>
    Private Function GetNumberStyles(ByVal allowFormatting As Boolean, ByVal allowCurrency As Boolean) As NumberStyles
        Dim styles As NumberStyles = NumberStyles.None

        If allowFormatting Then
            styles = styles Or NumberStyles.AllowThousands
        End If

        If allowCurrency Then
            styles = styles Or NumberStyles.AllowCurrencySymbol
        End If

        If Me.AllowNegative Then
            'A leading negative sign is always permitted.
            styles = styles Or NumberStyles.AllowLeadingSign

            If allowCurrency Then
                'Allow parentheses or a trailing negative sign only if the current currency pattern allows it.
                Select Case Me.GetFormatProvider().CurrencyNegativePattern
                    Case 0, 4, 14, 15 '($n), (n$), ($ n), (n $)
                        styles = styles Or NumberStyles.AllowParentheses
                    Case 3, 6, 7, 10, 11, 13 '$n-, n-$, n$-, n $-, $ n-, n- $
                        styles = styles Or NumberStyles.AllowTrailingSign
                End Select
            Else
                'Allow parentheses or a trailing negative sign only if the current number pattern allows it.
                Select Case Me.GetFormatProvider().NumberNegativePattern
                    Case 0 '(n)
                        styles = styles Or NumberStyles.AllowParentheses
                    Case 3, 4 'n-, n -
                        styles = styles Or NumberStyles.AllowTrailingSign
                End Select
            End If
        End If

        If Me.AllowDecimal Then
            styles = styles Or NumberStyles.AllowDecimalPoint
        End If

        Return styles
    End Function

    ''' <summary>
    ''' Gets the text that would result by inserting at the caret position and replacing selected text.
    ''' </summary>
    ''' <param name="partialText">
    ''' The text to be inserted.
    ''' </param>
    ''' <returns>
    ''' A <b>String</b> containing the complete new text.
    ''' </returns>
    Private Function GetProposedText(ByVal partialText As String) As String
        Dim currentText As String = Me.Text
        Dim selectionStart As Integer = Me.SelectionStart

        'To get the proposed text, insert the new text at the caret position, replacing any selected text.
        Return currentText.Substring(0, selectionStart) & _
               partialText & _
               currentText.Substring(selectionStart + Me.SelectionLength)
    End Function

    ''' <summary>
    ''' Pastes the text on the clipboard into the control.
    ''' </summary>
    Private Sub PasteClipboardText()
        Debug.Assert(Clipboard.ContainsText())

        Dim currentText As String = Me.Text
        Dim selectionStart As Integer = Me.SelectionStart
        Dim suffix As String = currentText.Substring(selectionStart + Me.SelectionLength)
        Dim clipboardText As String = Clipboard.GetText()
        Dim proposedText As String = Me.GetProposedText(clipboardText)

        Me.DisplayText(proposedText, False)

        currentText = Me.Text

        'Try to restore the caret to the correct position.
        If currentText = proposedText Then
            Me.SelectionStart = selectionStart + clipboardText.Length
        ElseIf currentText.EndsWith(suffix) Then
            Me.SelectionStart = currentText.Length - suffix.Length
        Else
            'The correct position cannot be determined so put the caret at the end of the text.
            Me.SelectionStart = currentText.Length
        End If
    End Sub

    ''' <summary>
    ''' Plays the <see cref="SystemSounds.Beep">Beep</see> sound to indicate that input was rejected if the current state supports it.
    ''' </summary>
    Private Sub PlayInvalidInputSound()
        If Me.BeepOnInvalidInput Then
            SystemSounds.Beep.Play()
        End If
    End Sub

    ''' <summary>
    ''' Determines whether a string would be valid to display in the control.
    ''' </summary>
    ''' <param name="text">
    ''' The proposed text.
    ''' </param>
    ''' <param name="allowFormatting">
    ''' Indicates whether the text is allowed to be formatted.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if the proposed text represents a valid value; otherwise, <b>false</b>.
    ''' </returns>
    ''' <remarks>
    ''' Any combination of a negative sign, currency symbol, decimal separator and group separators without any numbers are considered
    ''' valid as long as they don't explicitly violate the relevant pattern as defined by the current <see cref="NumberFormatInfo" />.
    ''' </remarks>
    Private Function ValidateText(ByVal text As String, ByVal allowFormatting As Boolean) As Boolean
        Return Me.ValidateTextForBlank(text, Not allowFormatting) OrElse _
               Me.ValidateTextForNumbers(text, allowFormatting) OrElse _
               Me.ValidateTextForCurrency(text, allowFormatting) OrElse _
               Me.ValidateTextForSpecialCases(text, allowFormatting)
    End Function

    ''' <summary>
    ''' Validates a string to determine whether it is empty and the control can accept blank input.
    ''' </summary>
    ''' <param name="text">
    ''' The text to validate.
    ''' </param>
    ''' <param name="ignoreBlankMode">
    ''' Indicates whether the text can be blank regardless of the <see cref="BlankMode" /> property value.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if the text is a valid blank value; otherwise, <b>false</b>.
    ''' </returns>
    Private Function ValidateTextForBlank(ByVal text As String, ByVal ignoreBlankMode As Boolean) As Boolean
        Return text = String.Empty AndAlso _
               (ignoreBlankMode OrElse Me.BlankMode = BlankModes.Accept)
    End Function

    ''' <summary>
    ''' Validates a string to determine whether it represents a valid currency value.
    ''' </summary>
    ''' <param name="text">
    ''' The text to be validated.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if <i>text</i> represents a valid currency value; otherwsie <b>false</b>.
    ''' </returns>
    Private Function ValidateTextForCurrency(ByVal text As String, ByVal allowFormatting As Boolean) As Boolean
        Dim formatProvider As NumberFormatInfo = Me.GetFormatProvider()
        Dim currencySymbol As String = formatProvider.CurrencySymbol
        Dim number As Decimal

        'The text must contain a currency symbol.
        Dim isValid As Boolean = text.Contains(currencySymbol)

        If isValid Then
            'Text must be able to be parsed.
            isValid = Decimal.TryParse(text, _
                                       Me.GetNumberStyles(allowFormatting, True), _
                                       formatProvider, _
                                       number)
        End If

        If isValid Then
            Dim currencyPattern As String = Me.GetCurrencyPattern(number)
            Dim patternCurrencyIndex As Integer = currencyPattern.IndexOf(currencySymbol)
            Dim patternNumberIndex As Integer = currencyPattern.IndexOf("n"c)
            Dim textCurrencyIndex As Integer = text.IndexOf(currencySymbol)
            Dim textNumberIndex As Integer = text.IndexOfAny(("1234567890" & _
                                                              formatProvider.CurrencyGroupSeparator & _
                                                              formatProvider.CurrencyDecimalSeparator).ToCharArray())

            'The number and currency symbol must be in the same order as in the current currency pattern.
            isValid = ((patternCurrencyIndex < patternNumberIndex) = (textCurrencyIndex < textNumberIndex))

            If isValid AndAlso number < Decimal.Zero Then
                If currencyPattern.Contains("-"c) Then
                    Dim patternNegativeIndex As Integer = currencyPattern.IndexOf("-"c)
                    Dim textNegativeIndex As Integer = text.IndexOf(formatProvider.NegativeSign)

                    'The negative sign and currency symbol must be in the same order as in the current currency pattern.
                    isValid = ((patternCurrencyIndex < patternNegativeIndex) = (textCurrencyIndex < textNegativeIndex))
                End If
            End If
        End If

        Return isValid
    End Function

    ''' <summary>
    ''' Validates a string to determine whether it represents a valid number.
    ''' </summary>
    ''' <param name="text">
    ''' The text to be validated.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if <i>text</i> represents a valid number; otherwsie <b>false</b>.
    ''' </returns>
    Private Function ValidateTextForNumbers(ByVal text As String, ByVal allowFormatting As Boolean) As Boolean
        Return Decimal.TryParse(text, _
                                Me.GetNumberStyles(allowFormatting, False), _
                                Me.GetFormatProvider(), _
                                New Decimal)
    End Function

    ''' <summary>
    ''' Validates a string to determine whether it matches one of the acceptable, non-numeric special cases.
    ''' </summary>
    ''' <param name="allowFormatting">
    ''' Indicates whether the special cases can contain formatting.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if the text matches a valid special case; otherwise, false.
    ''' </returns>
    ''' <remarks>
    ''' Special cases include various combinations including one or more of a negative sign, a currency symbol and a thousand separator.
    ''' Valid special cases depend on the number and currency patterns defined by the current format provider.
    ''' All special cases correspond to a value of zero.
    ''' </remarks>
    Private Function ValidateTextForSpecialCases(ByVal text As String, ByVal allowFormatting As Boolean) As Boolean
        Dim formatProvider As NumberFormatInfo = Me.GetFormatProvider()
        Dim negativeSign As String = formatProvider.NegativeSign
        Dim currencySymbol As String = formatProvider.CurrencySymbol
        Dim numberDecimalSeparator As String = formatProvider.NumberDecimalSeparator
        Dim currencyDecimalSeparator As String = formatProvider.CurrencyDecimalSeparator
        Dim specialCases As New List(Of String)

        If Me._allowNegative AndAlso Me._allowDecimal Then
            specialCases.AddRange(New String() {negativeSign, _
                                                numberDecimalSeparator, _
                                                negativeSign & numberDecimalSeparator})
        ElseIf Me._allowNegative AndAlso Not Me._allowDecimal Then
            specialCases.Add(negativeSign)
        ElseIf Not Me._allowNegative AndAlso Me._allowDecimal Then
            specialCases.Add(numberDecimalSeparator)
        End If

        If allowFormatting Then
            specialCases.AddRange(New String() {currencySymbol, _
                                                currencySymbol & currencyDecimalSeparator})

            If Me._allowNegative Then
                specialCases.AddRange(New String() {negativeSign & numberDecimalSeparator, _
                                                    negativeSign & currencySymbol, _
                                                    negativeSign & currencySymbol & currencyDecimalSeparator})

                Select Case formatProvider.NumberNegativePattern
                    Case 0 '(n)
                        specialCases.AddRange(New String() {"()", _
                                                            "(" & numberDecimalSeparator & ")"})
                    Case 2 '- n
                        specialCases.AddRange(New String() {negativeSign & " ", _
                                                            negativeSign & " " & numberDecimalSeparator})
                    Case 3 'n-
                        specialCases.Add(numberDecimalSeparator & negativeSign)
                    Case 4 'n -
                        specialCases.AddRange(New String() {" " & negativeSign, _
                                                            numberDecimalSeparator & " " & negativeSign})
                End Select

                Select Case formatProvider.CurrencyNegativePattern
                    Case 0 '($n)
                        specialCases.AddRange(New String() {"(" & currencySymbol & ")", _
                                                            "(" & currencySymbol & currencyDecimalSeparator & ")"})
                    Case 1 '-$n
                        specialCases.AddRange(New String() {negativeSign & currencySymbol, _
                                                            negativeSign & currencySymbol & currencyDecimalSeparator})
                    Case 2 '$-n
                        specialCases.AddRange(New String() {currencySymbol & negativeSign, _
                                                            currencySymbol & negativeSign & currencyDecimalSeparator})
                    Case 3 '$n-
                        specialCases.AddRange(New String() {currencySymbol & negativeSign, _
                                                            currencySymbol & currencyDecimalSeparator & negativeSign})
                    Case 4 '(n$)
                        specialCases.AddRange(New String() {"(" & currencySymbol & ")", _
                                                            "(" & currencyDecimalSeparator & currencySymbol & ")"})
                    Case 5 '-n$
                        specialCases.AddRange(New String() {negativeSign & currencySymbol, _
                                                            negativeSign & currencyDecimalSeparator & currencySymbol})
                    Case 6 'n-$
                        specialCases.AddRange(New String() {negativeSign & currencySymbol, _
                                                            currencyDecimalSeparator & negativeSign & currencySymbol})
                    Case 7 'n$-
                        specialCases.AddRange(New String() {currencySymbol & negativeSign, _
                                                            currencyDecimalSeparator & currencySymbol & negativeSign})
                    Case 8 '-n $
                        specialCases.AddRange(New String() {negativeSign & " " & currencySymbol, _
                                                            negativeSign & currencyDecimalSeparator & " " & currencySymbol})
                    Case 9 '-$ n
                        specialCases.AddRange(New String() {negativeSign & currencySymbol, _
                                                            negativeSign & currencySymbol & " " & currencyDecimalSeparator})
                    Case 10 'n $-
                        specialCases.AddRange(New String() {currencySymbol & negativeSign, _
                                                            currencyDecimalSeparator & " " & currencySymbol & negativeSign})
                    Case 11 '$ n-
                        specialCases.AddRange(New String() {currencySymbol & " " & negativeSign, _
                                                            currencySymbol & " " & currencyDecimalSeparator & negativeSign})
                    Case 12 '$ -n
                        specialCases.AddRange(New String() {currencySymbol & " " & negativeSign, _
                                                            currencySymbol & " " & negativeSign & currencyDecimalSeparator})
                    Case 13 'n- $
                        specialCases.AddRange(New String() {negativeSign & " " & currencySymbol, _
                                                            currencyDecimalSeparator & negativeSign & " " & currencySymbol})
                    Case 14 '($ n)
                        specialCases.AddRange(New String() {"(" & currencySymbol & ")", _
                                                            "(" & currencySymbol & " " & currencyDecimalSeparator & ")"})
                    Case 15 '(n $)
                        specialCases.AddRange(New String() {"(" & currencySymbol & ")", _
                                                            "(" & currencyDecimalSeparator & " " & currencySymbol & ")"})
                End Select
            End If
        End If

        Return specialCases.Contains(text)
    End Function

    ''' <summary>
    ''' Validates the specified number.
    ''' </summary>
    ''' <param name="number">
    ''' The proposed number.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if the number is valid; otherwise, <b>false</b>.
    ''' </returns>
    Private Function ValidateNumber(ByVal number As Decimal?) As Boolean
        Dim isValid As Boolean

        If number.HasValue Then
            Dim value = number.Value

            'Confirm that the number satisfies the current rules.
            isValid = (value >= Decimal.Zero OrElse Me.AllowNegative) AndAlso _
                      (value = Decimal.Truncate(value) OrElse Me.AllowDecimal)
        Else
            'Confirm that blank values are allowed.
            isValid = (Me.BlankMode = BlankModes.Accept)
        End If

        If Not isValid Then
            Me.PlayInvalidInputSound()
        End If

        Return isValid
    End Function

    ''' <summary>
    ''' Validates the text that would result from inserting new text at the caret position.
    ''' </summary>
    ''' <param name="partialText">
    ''' The new text being inserted.
    ''' </param>
    ''' <returns>
    ''' <b>true</b> if the proposed text represents a valid number; otherwise, <b>false</b>.
    ''' </returns>
    Private Function ValidatePartialText(ByVal partialText As String) As Boolean
        Return Me.ValidateText(Me.GetProposedText(partialText), _
                               Not Me.Focused)
    End Function

    ''' <summary>
    ''' Validates the text that would result from pasting the current contents of the clipboard into the control.
    ''' </summary>
    ''' <returns>
    ''' <b>true</b> if the proposed text represents a valid number; otherwise, <b>false</b>.
    ''' </returns>
    Private Function ValidatePastedText() As Boolean
        Return Me.ValidateText(Me.GetProposedText(Clipboard.GetText()), _
                               True)
    End Function

#End Region 'Private Methods

#End Region 'Methods

End Class
 
It's also worth noting that you can display a prompt in a TextBox without setting the Text, using the SendMessage API and the EM_SETCUEBANNER message. I'm not going to go into detail here as there are many examples on the web.
 
Note that I generally wouldn't use those custom controls myself. I'd generally use a NumericUpDown, which will do the work for you, or else just let the user type whatever they want into a TextBox and then validate on the Validating event. Even the NumericUpDown will let the user enter multiple negative signs or separators and tell them when they try to leave.
 
First of all, I would like to thank @Skydiver and @jmcilhinney for their replies. A lot of wisdom here.
I figure out another solution that worked for me and it is quite simple, so I would like to share with you guys. Hope this could help somebody.

I added a using System.Globalization (that allows another options in changing the number culture format.)
Before, I was using the code "bwA = Double.Parse(tb_bwA.Text);" to input the values to my program.
Now, my code is
Input number code:
using System.Globalization;

bwA = Double.Parse(tb_bwA.Text, CultureInfo.InvariantCulture);
//The "CultureInfo.InvariantCulture" after the coma allows me to input values
//using a point (Example: 0.0) rather than a coma (Example: 0,0) and
//consequently ignore my system culture "pt-BR" that only allows a coma as a separator

As I didn´t changed my output Textbox code, the results are given in the current system culture of the user (in my case "pt-BR" and a coma as a separator).
 
Why stop there? Why not set your entire app domain's UI and current culture to Invariant culture? That way you don't need to remember to use that variant of Parse() or TryParse()?
 
Back
Top Bottom