Search code examples
c#wpfxamlstring-formattingcurrency-formatting

Format as currency with no trailing zeroes in wpf xaml


Assuming we have a viewmodel property MyMoney. How can I format it in its view xaml as a currency with no trailing zeroes?

For example:

MyMoney = 1; //$1
MyMoney = 1.2 //$1.2

I've tried the following in xaml (e.g. <TextBox Text="{Binding MyMoney, StringFormat=..."/>) but it doesn't satisfy all the conditions:

StringFormat=C shows currency but also trailing zeroes.
StringFormat=C0 shows currency but shows only the whole number.
StringFormat={}{0:0.##} does not show trailing zeroes but not as currency.
StringFormat={}{0:$0.##} does not show trailing zeroes but hard-coded $. We should be able to cater current locale/culture's currency.


Solution

  • First, I would like to give credit to @Andy for his answer which led me to use IValueConverter.

    I'm posting my solution which offers the following benefits:

    1. Leverage C#'s "C" format specifier

      a. Consider negative values (e.g. -1 --> ($1))

      b. Cater varying current locale/culture

    2. Bind to multiple data types (decimal, double, int, etc).

    3. Return DependencyProperty.UnsetValue when ConvertBack is unable to produce a value.

    All of the above follow how StringFormat=c will behave in wpf (e.g. in a TextBox) except the converter removes trailing zeroes as desired.


    public class CurrencyFormatConverter : MarkupExtension, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
            System.Convert.ToDecimal(value).ToCurrency(culture);
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            switch (Type.GetTypeCode(targetType))
            {
                case TypeCode.Decimal:
                    return Decimal.TryParse(value.ToString(), NumberStyles.Currency, culture, out var @decimal)
                        ? @decimal
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.Double:
                    return Double.TryParse(value.ToString(), NumberStyles.Currency, culture, out var @double)
                        ? @double
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.Int16:
                    return Int16.TryParse(value.ToString(), NumberStyles.Currency, culture, out var int16)
                        ? int16
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.Int32:
                    return Int32.TryParse(value.ToString(), NumberStyles.Currency, culture, out var int32)
                        ? int32
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.Int64:
                    return Int64.TryParse(value.ToString(), NumberStyles.Currency, culture, out var int64)
                        ? int64
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.Single:
                    return Single.TryParse(value.ToString(), NumberStyles.Currency, culture, out var single)
                        ? single
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.UInt16:
                    return UInt16.TryParse(value.ToString(), NumberStyles.Currency, culture, out var uint16)
                        ? uint16
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.UInt32:
                    return UInt32.TryParse(value.ToString(), NumberStyles.Currency, culture, out var uint32)
                        ? uint32
                        : DependencyProperty.UnsetValue;
    
                case TypeCode.UInt64:
                    return UInt64.TryParse(value.ToString(), NumberStyles.Currency, culture, out var uint64)
                        ? uint64
                        : DependencyProperty.UnsetValue;
    
                default:
                    throw new NotSupportedException($"Converting currency string to target type {targetType} is not supported.");
            }
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider) => this;
    }
    

    More info on ToCurrency here

    public static class DecimalExtensions
    {
        /// <summary>
        ///     Converts a numeric value to its equivalent currency string representation using the specified culture-specific format information.
        /// </summary>
        /// <param name="value">The value to be converted.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
        /// <returns>The currency string representation of the value as specified by <paramref name="provider" />.</returns>
        public static string ToCurrency(this decimal value, IFormatProvider provider) =>
            /// Use "1" (or "-1" if value is negative)
            /// as a placeholder for the actual value.
            (value < 0 ? -1 : 1)
    
            /// Format as a currency using the "C" format specifier.
            .ToString("C0", provider)
    
            /// Convert the absolute value to its string representation
            /// then replace the placeholder "1".
            /// We used absolute value since the negative sign
            /// is already converted to its string representation
            /// using the "C" format specifier.
            .Replace("1", Math.Abs(value).ToString("#,0.############################", provider));
    }