Search code examples
xamarindecimalxamarin.formscurrency

Xamarin.Forms Entry with auto thousand and decimal separator


I have a Xamarin.Forms entry with numeric keyboard that will represent a pt-BR REAL currency (999.999,99). When I type numbers in the numeric keyboard, the comma(representing decimal) and dot(representind thousand) needs to be added automatically while I am typing.

To achieve this goal, what is the best practice/design pattern in Xamarin.Forms to work in all platforms?


Solution

  • The trick is to use a TextChanged event. The first step I removed the $ from the string so that I could parse the new text value. If it fails to parse, that means that the user added a non-digit character and we just revert to whatever the old text was.

    Next, we detect if user ADDED a new digit and its to the RIGHT of the decimal (example 1.532). If so we, we move the decimal to the right by * 10. Do the opposite for a DELETION.

    OH, and almost forgot about when we initialize the number! The first digit we enter will be a whole number so we * 100 to make sure the first digit we enter starts as fraction.

    Once we got our decimal correct, we display it using num.ToString("C");

    Working Example: enter image description here

    xaml:

    <Entry                 
        Keyboard="Numeric"
        TextChanged="OnFinancialTextChanged"
        Placeholder="$10.00"
        Text="{Binding RetailPrice}"/>
    

    Then in the cs

    .cs:

    private void OnFinancialTextChanged(object sender, TextChangedEventArgs e)
    {
        var entry = (Entry)sender;
                       
        var amt = e.NewTextValue.Replace("$", "");
    
    
        if (decimal.TryParse(amt, out decimal num))
        {
            // Init our number
            if(string.IsNullOrEmpty(e.OldTextValue))
            {
                num = num / 100;
            }
            // Shift decimal to right if added a decimal digit
            else if (num.DecimalDigits() > 2 && !e.IsDeletion())
            {
                num = num * 10;
            }
            // Shift decimal to left if deleted a decimal digit
            else if(num.DecimalDigits() < 2 && e.IsDeletion())
            {
                num = num / 10;
            }
    
            entry.Text = num.ToString("C");
        }
        else
        {
            entry.Text = e.OldTextValue;
        }
    }
    

    I created these Extension methods to help with the logic

    public static class ExtensionMethods
    {
        public static int DecimalDigits(this decimal n)
        {
            return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                    .SkipWhile(c => c != '.')
                    .Skip(1)
                    .Count();
        }
    
        public static bool IsDeletion(this TextChangedEventArgs e)
        {
            return !string.IsNullOrEmpty(e.OldTextValue) && e.OldTextValue.Length > e.NewTextValue.Length;
        }
    }