Форматировать как валюту без конечных нулей в wpf xaml

Предположим, у нас есть свойство модели представления MyMoney. Как я могу отформатировать его в представлении xaml как валюту без конечных нулей?

Например:

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

Я пробовал следующее в xaml (например, <TextBox Text="{Binding MyMoney, StringFormat=..."/>), но он не удовлетворяет всем условиям:

StringFormat=C показывает валюту, но также и нули в конце.
StringFormat=C0 показывает валюту, но показывает только целое число.
StringFormat={}{0:0.##} не показывает нули в конце, но не в виде валюты.
StringFormat={}{0:$0.##} не показывает нули в конце, но жестко задает $. Мы должны быть в состоянии обслуживать текущую местную/культурную валюту.


person Jan Paolo Go    schedule 14.12.2018    source источник
comment
Примечание: убедитесь, что вы понимаете, что форматы C не знают, в какой валюте ваши значения, и используют символ валюты для текущей локали... Хотя часто эти два значения совпадают, это не обязательно, т.е. если ваши транзакции в долларах США, и вы показываете страницу в JA-JP, вы получите неожиданные цены :) В любом случае вам может потребоваться выполнить пользовательское форматирование (выбор символа валюты вручную и объединение с g)   -  person Alexei Levenkov    schedule 14.12.2018
comment
@AlexeiLevenkov Ах да. позвольте мне обновить мой вопрос. Спасибо что подметил это :)   -  person Jan Paolo Go    schedule 14.12.2018
comment
А если использовать конвертер?   -  person Kaspar    schedule 14.12.2018


Ответы (3)


Я думаю, что вам в значительной степени нужно использовать преобразователь здесь из-за требования удалить конечные нули. Это, вероятно, требует немного больше работы, но:

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace wpf_99
{
public class CurrencyFormatConverter : MarkupExtension, IValueConverter
{
// public double Multiplier { get; set; } You could pass parameters to properties.

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol + System.Convert.ToDecimal(value).ToString("0.##");
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string input = value.ToString();
        if(!char.IsDigit(input[0]))
        {
            input= input.Substring(1);
        }
        if(input.Length == 0)
        {
            return 0;
        }
        return Decimal.Parse(input);
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}
}

использование

<TextBox Text="{Binding Money,UpdateSourceTrigger=PropertyChanged, Converter={local:CurrencyFormatConverter}}"/>
person Andy    schedule 14.12.2018
comment
Благодарю вас! Мне нравится этот подход. Для чего нужен множитель? - person Jan Paolo Go; 15.12.2018
comment
Я скопировал код из преобразователя множителей, чтобы сэкономить время на интерфейсах. :^) Однако вы можете передать символ валюты, который вы хотите, в качестве параметра для такого свойства. Возможно, как необязательный параметр. - person Andy; 15.12.2018
comment
Я понимаю. Другой вопрос, зачем использовать CultureInfo.CurrentCulture вместо параметра culture? - person Jan Paolo Go; 16.12.2018
comment
Спасибо за этот подход, и я принял его как ответ, поскольку использование IValueConverter кажется хорошим/чистым способом добиться пользовательского форматирования в wpf. - person Jan Paolo Go; 16.12.2018
comment
Я опубликовал новый вопрос, чтобы сосредоточиться на других тестовых примерах форматирования валюты, таких как отрицательные числа, другая культура, разные разделители чисел и т. д. stackoverflow.com/questions/53798761/< /а> - person Jan Paolo Go; 16.12.2018

Вероятно, вы бы знали, что «C» в строковом формате работает в соответствии с локалью/культурой, установленной на вашем локальном компьютере. Однако, чтобы ответить на ваш вопрос, я бы предложил поместить код удаления завершающих нулей на уровне свойства и сохранить форматирование строки. на уровне xaml как можно проще. Например, для

На уровне xaml:

  <TextBox x:Name="TextBox_Curr" Height="50" Text="{Binding Money,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,StringFormat={}{0:C}}" Margin="120,134,156,135"/>

На уровне модели (просто необработанный код):

  private decimal _money;
        public decimal Money
        {
            get { return _money; }
            set {

                 _money = value;
                _money.ToString("0.##");

                 NotifyPropertyChanged("Money"); }
        }

Это сработало для меня при запуске примера кода.

Кроме того, если вы не хотите изменять системные настройки, вы, вероятно, можете выбрать принудительную настройку культуры.

public static class Cultures
{
    public static readonly CultureInfo UnitedKingdom = 
        CultureInfo.GetCultureInfo("en-GB");
}
Then:

Money.ToString("C", Cultures.UnitedKingdom)
person AlphaWarrior    schedule 14.12.2018

Во-первых, я хотел бы отдать должное @Andy за его ответ, который побудил меня использовать IValueConverter.

Я публикую свое решение, которое предлагает следующие преимущества:

  1. Используйте описатель формата "C"

    а. Рассмотрите отрицательные значения (например, -1 --> ($1))

    б. Обеспечить различные текущие регионы/культуры

  2. Привязка к нескольким типам данных (decimal, double, int и т. д.).

  3. Вернуть DependencyProperty.UnsetValue когда ConvertBack не может создать значение.

Все вышеперечисленное соответствует тому, как StringFormat=c будет вести себя в wpf (например, в TextBox), за исключением того, что преобразователь удаляет конечные нули по желанию.


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;
}

Подробнее о ToCurrency здесь< /а>

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));
}
person Jan Paolo Go    schedule 16.12.2018