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.
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:
Leverage C#'s "C" format specifier
a. Consider negative values (e.g. -1 --> ($1)
)
b. Cater varying current locale/culture
Bind to multiple data types (decimal
, double
, int
, etc).
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;
}
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));
}