Link to home
Start Free TrialLog in
Avatar of ekenman
ekenman

asked on

Thousand separator and decimals in textbox

I'd like to input numbers in a textbox and format it with a thousand separator and two decimals.

I've tried the Masked Edit with a lot of different masks, but I just can't get it to work.

I've also tried to put some code at the TextChanged event using TextBox1.Text = val(TextBox1.Text).Tostring("#,#.##"). This will only work for some globalisations though. Most users of this app have Swedish settings where decimal points are decimal commas.

Any suggestions are much appreciated.
Avatar of abel
abel
Flag of Netherlands image

The masked edit is meant for edits that have a defined amount of characters (like a date), at least, as soon as you use placeholders that are supposed to be on the display string (dashes or slashes in dates, dots or commas in numbers).

I recently created a nice workaround for someone else on EE. Have a look, it shows you how you can do just this. It probably needs a bit of tweaking, just tell me where you need some additional help: https://www.experts-exchange.com/questions/24340590/MaskedEditTextbox-mask-numeric.html?#24209640
Avatar of ekenman
ekenman

ASKER

Thank's abel!
Yes... I've started on my own numericTextBox but it just seems like a lot of work for something that must be an issue for many programmers. Isn't there an easier way to do it?
I'll leave the question open today but will assign you the points if nothing easier comes up.
Thanks again
Tomas
TextBox1.Text = FormatNumber(val(TextBox1.Text).tostring,2)
The "easier way" is using the best of both worlds by adding a key handler like in that example, which does the formatting when a user types a comma or a dot. At least, that's the "easiest" I could find at the time...
@JackOfPH: but then you do not have the benefits of the masked edit control anymore....
Avatar of ekenman

ASKER

JackOfPH, That will ignore the local globalisation settings. If I enter:"123,23" which is the Swedish way of entering "123.23" it wont work.
ASKER CERTIFIED SOLUTION
Avatar of abel
abel
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ekenman

ASKER

Yes... Now I'm getting close. I'll put the code of my NumericTextBox here when done and assign points to you abel.
If you do this on the onchange or on the keypress event, you probably want to store the selectionstart value of the textbox, to prevent that the user's cursor is being moved away whenever he adds something in the middle or at the beginning of the textbox.
Avatar of ekenman

ASKER

yes... I'm working on it now :). It's a hassle though since the parsed text is changed. but I'm getting there. I've gotten this far...
    Protected Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs)
        MyBase.OnKeyPress(e)
 
        Dim localNumberFormat As NumberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
        Dim decimalSeparator As String = localNumberFormat.NumberDecimalSeparator
        Dim groupSeparator As String = localNumberFormat.NumberGroupSeparator
        Dim negativeSign As String = localNumberFormat.NegativeSign
        If Asc(groupSeparator) = 160 Then groupSeparator = " " 'Change the groupseparator to ASCII 32 (space) from 160 (inserted space)
        Dim keyInput As String = e.KeyChar.ToString()
 
 
 
        If [Char].IsDigit(e.KeyChar) Then
        
        ElseIf keyInput.Equals(decimalSeparator) OrElse keyInput.Equals(groupSeparator) OrElse keyInput.Equals(negativeSign) Then
            ' Nothing needs to be done
        ElseIf e.KeyChar = vbBack Then
            ' Nothing needs to be done
        Else
            'Ignore the key entered
            e.Handled = True
            Beep()
        End If
 
    End Sub
 
    Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
        Dim localNumberFormat As NumberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
        Dim decimalSeparator As String = localNumberFormat.NumberDecimalSeparator
        Dim groupSeparator As String = localNumberFormat.NumberGroupSeparator
        Dim negativeSign As String = localNumberFormat.NegativeSign
        If Asc(groupSeparator) = 160 Then groupSeparator = " " 'Change the groupseparator to ASCII 32 (space) from 160 (inserted space)
 
        Dim oldselectionstart As Integer = SelectionStart
        Dim oldtextlength As Integer = Len(Text)
        If Microsoft.VisualBasic.Right(Text, 1) = decimalSeparator Or (Mid(Text, 1) = negativeSign And Len(Text) = 1) Then 'Dont remove minussign or decimal sign
        Else
            Dim dbl As Double = 0.0
            Double.TryParse(Text, dbl)
            Text = dbl.ToString("#,#.##")
            SelectionStart = oldselectionstart + 1
        End If
        
    End Sub

Open in new window

Avatar of ekenman

ASKER

Ok, I've got a working class I believe that I put here for others to use. Thanks for your help abel
Imports System.Globalization
Public Class NumericTextBox
    Inherits TextBox
    Private Changingtext As Boolean = False
    Protected Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs)
        MyBase.OnKeyPress(e)
 
        Dim localNumberFormat As NumberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
        Dim decimalSeparator As String = localNumberFormat.NumberDecimalSeparator
        Dim groupSeparator As String = localNumberFormat.NumberGroupSeparator
        Dim alternativeSeparator As String = ""
        Dim negativeSign As String = localNumberFormat.NegativeSign
        'If Asc(groupSeparator) = 160 Then alternativeSeparator = " " 'Add alterantivegroupseparator to ASCII 32 (space) from 160 (inserted space)
        Dim keyInput As String = e.KeyChar.ToString()
        
 
        If [Char].IsDigit(e.KeyChar) Then
            'Do nothing
        ElseIf keyInput.Equals(decimalSeparator) OrElse keyInput.Equals(groupSeparator) OrElse keyInput.Equals(negativeSign) Then
            ' Nothing needs to be done
        ElseIf e.KeyChar = vbBack Then
            'If we're at a groupseparator, there won't be a differense to the ontextchange so we need to remove an extra.
            If Mid(Text, SelectionStart, 1) = groupSeparator Then
                Changingtext = True
                Dim oldselectionstart As Integer = SelectionStart
                Text = Mid(Text, 1, SelectionStart - 2) & Mid(Text, SelectionStart)
                SelectionStart = oldselectionstart
                Changingtext = False
                Text = Text.Replace(groupSeparator, "")
                SelectionStart = oldselectionstart - 1
                e.Handled = True
            End If
        Else
            'Ignore the key entered
            e.Handled = True
            Beep()
        End If
 
    End Sub
 
    Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
        If Not Changingtext Then
            Changingtext = True
            Dim localNumberFormat As NumberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
            Dim decimalSeparator As String = localNumberFormat.NumberDecimalSeparator
            Dim groupSeparator As String = localNumberFormat.NumberGroupSeparator
            Dim negativeSign As String = localNumberFormat.NegativeSign
            If Asc(groupSeparator) = 160 Then groupSeparator = " " 'Change the groupseparator to ASCII 32 (space) from 160 (inserted space)
 
            Dim oldselectionstart As Integer = SelectionStart
            Dim oldtext As String = Text
            If Microsoft.VisualBasic.Right(Text, 1) = decimalSeparator Or (Mid(Text, 1) = negativeSign And Len(Text) = 1) Then 'Dont remove minussign or decimal sign
            Else
                Dim dbl As Double = 0.0
                Double.TryParse(Text, dbl)
                Text = dbl.ToString("#,#.##")
            End If
            If Len(oldtext) <> Len(Text) Then
                SelectionStart = oldselectionstart + 1
            Else
                SelectionStart = oldselectionstart
            End If
            Changingtext = False
        End If
    End Sub
End Class

Open in new window

That looks quite daunting. Since you are doing all by yourself... Instead of getting all this information from the culture, you can also use the .ToString("N2") which I showed you, which does all this replacement automatically, but then you need to maintain the position of the cursor.

Note that all conversion functions of the .Format, FormatXXX and .ToString(...) use the current culture by default.
Avatar of ekenman

ASKER

The .tostring("N2") does not give me separators for thousands. But I do use the .tostring("#,#.##") and that's the same then isn't it?
Then I wanted to limit keycahrs allowed to numbers.
Then if I start with the "-" sign, the .tostring removes that since "-" = 0..., also if I put a decimal sign in the tostring is going to treat it as .0 and remove it. Then if the cursor was to the right of a groupseparator and the vbBack-key was pressed, it would only remove the separator . So I can't figure out any other way to make this work.
I'm sure it's not the neatest way of doing it but at least it seems to be working. I'm always open to suggestions of making it better though!
Thanks for your time and effort!
 
> The .tostring("N2") does not give me separators for thousands.

interesting, because it should, unless in the Language Settings in your windows version, the thousand separator is blank.

Anyway, you've got a working version now. It has taken myself a long time at the time to understand all that's going on with this formatting....