1

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.

Pspl
  • 1,398
  • 12
  • 23
  • Rather than text changed, I'd use the `Leave` event so that you dont have to contend with the decimal chars you put in it. Then use `ToString(0` to use whatever decimal char the culture uses – Ňɏssa Pøngjǣrdenlarp Jul 03 '17 at 14:44
  • You are using the wrong event, use the Validating event instead. Decimal.TryParse + Decimal.ToString ought to be useful. – Hans Passant Jul 03 '17 at 14:44
  • https://stackoverflow.com/questions/16035506/format-a-number-with-commas-and-decimals-in-c-sharp-asp-net-mvc3 a c# answer to a relatively similar question, it might help you out! – Kevin Jul 03 '17 at 14:47
  • @Plutonix, @Hans Passant, sorry guys, but both `Leave` and `Validating` events occurs when you're done with the output, I'm pretty sure. I want the code running as long as the user fill the `TextBox`. – Pspl Jul 03 '17 at 14:56
  • @Kevin, I'll check it out. – Pspl Jul 03 '17 at 14:57
  • Sonds like you are recreating the `NumericUpDown` - numerals only, decimal display. – Ňɏssa Pøngjǣrdenlarp Jul 03 '17 at 15:01

2 Answers2

4

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!

Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
1

Thank you @Visual Vincent. Your method works fine. However I managed to find a simpler way of doing what I asked by the following code:

Private Sub TextBox_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox.KeyPress
    If Not Char.IsDigit(e.KeyChar) And Not Char.IsControl(e.KeyChar) Then
        e.Handled = True
    End If
End Sub
Private Sub TextBox_TextChanged(sender As Object, e As EventArgs) Handles TextBox.TextChanged
    Select Case Val(Replace(Me.TextBox.Text, ",", "."))
        Case 0 : Me.TextBox.Text = ""
        Case Else
            Me.TextBox.Text = Format(Val(Replace(Me.TextBox.Text, ",", "")) / 100, "0.00")
            Me.TextBox.SelectionStart = Len(Me.TextBox.Text)
    End Select
End Sub

This piece of code look suspicious simple to me. For now it works fine and does the trick exactly how I wanted. Maybe there's something missing to me, or maybe I wasn't clear enough on the description of my goal. If you find any flaw on my method, please feel free to point it! I'll appreciate it very much.

Pspl
  • 1,398
  • 12
  • 23
  • I guess the only thing to point out would be that this too will adapt to the system's decimal point. Thus trying to replace `","` with `""` won't work on for example an American system. Otherwise I suppose it should work. :) – Visual Vincent Jul 04 '17 at 07:39
  • Maybe that's why it's working... For some things, it's good not being an american, eh eh eh... Thanks a lot for your reply :) – Pspl Jul 04 '17 at 07:43
  • Well the only thing you have to do is a second replace and it should work: `Replace(Replace(Me.TextBox.Text, ",", ""), ".", "")` – Visual Vincent Jul 04 '17 at 07:48
  • One thing I just noticed (and I cannot test this since I'm on vacation) is that shouldn't changing `Me.TextBox.Text` inside `TextChanged` cause the event to be raised indefinitely over and over again? – Visual Vincent Jul 04 '17 at 08:17