Search code examples
vb.nettextboxdecimal

Automatic decimal places in textbox


It looks very strange, but I can't find an online solution for my problem! At least in VB.NET.

Here's the deal: I have a TextBox in a form (limited to numbers by a KeyPress event) and want to keep two decimal places as long as the user inputs his data.

For example, if the TextBox is blank, then, when the user presses, let's say, "2", the TextBox shows "0,02". Then, if the user presses "7", the TextBox shows "0,27". Then again, by pressing "6", it shows "2,76" and so on...

I managed to do this for one decimal place with the code:

    Select Case Me.TextBox.Text
        Case ""
        Case ","
            Me.TextBox.Text = ""
        Case Else
            Me.TextBox.Text = Strings.Left(Replace(Me.TextBox.Text, ",", ""), Strings.Len(Replace(Me.TextBox.Text, ",", "")) - 1) & "," & Strings.Right(Replace(Me.TextBox.Text, ",", ""), 1)
            Me.TextBox.SelectionStart = Len(Me.TextBox.Text)
    End Select

Please note that: 1. This code's running on a TextChanged event; 2. I'm from Portugal and here we use a comma (",") instead of a dot (".") for the decimal separator.

Could you help me to adjust my piece of code to work properly with two decimal places?

Any help will be very appreciated. And, as always, thank you all in advance.


Solution

  • Here's a custom class I've made which does what you require:

    Public Class FactorDecimal
        Private _value As String = "0"
    
        Public DecimalPlaces As Integer
    
        Public Sub AppendNumber(ByVal Character As Char)
            If Char.IsNumber(Character) = False Then Throw New ArgumentException("Input must be a valid numerical character!", "Character")
            _value = (_value & Character).TrimStart("0"c)
        End Sub
    
        Public Sub RemoveRange(ByVal Index As Integer, ByVal Length As Integer)
            If _value.Length >= Me.DecimalPlaces + 1 AndAlso _
                Index + Length > _value.Length - Me.DecimalPlaces Then Length -= 1 'Exclude decimal point.
    
            If Index + Length >= _value.Length Then Length = _value.Length - Index 'Out of range checking.
    
            _value = _value.Remove(Index, Length)
    
            If _value.Length = 0 Then _value = "0"
        End Sub
    
        Public Overrides Function ToString() As String
            Dim Result As Decimal
            If Decimal.TryParse(_value, Result) = True Then
                'Divide Result by (10 ^ DecimalPlaces) in order to get the amount of decimal places we want.
                'For example: 2 decimal places   =>   Result / (10 ^ 2) = Result / 100 = x,xx.
                Return (Result / (10 ^ Me.DecimalPlaces)).ToString("0." & New String("0"c, Me.DecimalPlaces))
            End If
            Return "<parse error>"
        End Function
    
        Public Sub New(ByVal DecimalPlaces As Integer)
            If DecimalPlaces <= 0 Then DecimalPlaces = 1
            Me.DecimalPlaces = DecimalPlaces
        End Sub
    End Class
    

    It works by letting you append numbers to form a long string of numerical characters (for example 3174 + 8 = 31748), then when you call ToString() it does the following:

    • It parses the long number string into a decimal (ex. "31748" => 31748.0)

    • It divides the decimal by 10 raised to the power of the amount of decimals you want (for example: 2 decimals => 31748.0 / 102 = 317.48).

    • Finally it calls ToString() on the decimal with the format 0.x - where x is a repeating amount of zeros depending on how many decimals you want (ex. 2 decimals => 0.00).

    NOTE: This solution adapts to the current system's culture settings and will therefore automatically use the decimal point defined in that culture. For example in an American (en-US) system it will use the dot: 317.48, whereas in a Swedish (sv-SE) or Portuguese (pt-PT) system it will use the comma: 317,48.

    You can use it like this:

    Dim FactorDecimal1 As New FactorDecimal(2)
    
    Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
        If Char.IsNumber(e.KeyChar) = False Then
            e.Handled = True 'Input was not a number.
            Return
        End If
    
        FactorDecimal1.AppendNumber(e.KeyChar)
        TextBox1.Text = FactorDecimal1.ToString()
    End Sub
    
    Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyDown
        Dim TargetTextBox As TextBox = DirectCast(sender, TextBox)
        e.SuppressKeyPress = True
    
        Select Case e.KeyData 'In order to not block some standard keyboard shortcuts (ignoring paste since the pasted text won't get verified).
            Case Keys.Control Or Keys.C
                TargetTextBox.Copy()
            Case Keys.Control Or Keys.X
                TargetTextBox.Cut()
            Case Keys.Control Or Keys.A
                TargetTextBox.SelectAll()
            Case Keys.Back, Keys.Delete 'Backspace or DEL.
                FactorDecimal1.RemoveRange(TextBox1.SelectionStart, If(TextBox1.SelectionLength = 0, 1, TextBox1.SelectionLength))
                TextBox1.Text = FactorDecimal1.ToString()
            Case Else
                e.SuppressKeyPress = False 'Allow all other key presses to be passed on to the KeyPress event.
        End Select
    End Sub
    

    Online test: http://ideone.com/fMcKJr

    Hope this helps!