扩展内置类型的自定义格式功能

时间:2013-11-25 03:40:44

标签: c# .net formatting

我对decimal值有一些相当尴尬的格式要求。简而言之:显示带有尾随空格的两位小数,除非第三个小数是5,在这种情况下显示为三位小数。

这种格式化也需要相当灵活。具体来说,并不总是需要尾随空格,当第三个小数是“5”时,可能首选“½”。

示例:

  • 1.13将显示为“01.13”,带有空格或“01.13”显示为
  • 1.315将显示为“01.315”或“01.31½”

我需要在其他不相关的UI部分中使用此逻辑。我暂时把它写成了WPF值转换器,但这只是为了演示:

public sealed class PriceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is decimal))
        {
            return DependencyProperty.UnsetValue;
        }

        var decimalValue = (decimal)value;
        var formattedDecimalValue = decimalValue.ToString("#0.000", CultureInfo.InvariantCulture);
        var lastFormattedChar = formattedDecimalValue[formattedDecimalValue.Length - 1];

        switch (lastFormattedChar)
        {
            case '0':
                return formattedDecimalValue.Substring(0, formattedDecimalValue.Length - 1) + " ";
            case '5':
                return formattedDecimalValue.Substring(0, formattedDecimalValue.Length - 1) + "½";
            default:
                return formattedDecimalValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我现在正试图将其提取到一个更基本的构建块中,我可以在整个UI层中使用它。我最初的想法是一个自定义格式提供程序,然后我可以使用Binding

<TextBlock Text="{Binding Value, FormatString=WHATEVER}"/>

这个想法是格式字符串可能是“#0.005”,它表示只显示第三个小数位,如果它是5,或“#0.00F”,它试图将第三个小数表示为分数。但是,我无法从绑定中找到使用特定格式提供程序的方法,这似乎是对我的一个主要限制,但也许我错过了一些东西......?

经过更多的实验和调查,我得出结论,我唯一的选择是定义我自己的类型:

public struct Price : IFormattable

此类型将封装我需要的额外格式化功能。但是,现在我还有另一个难题:在我的ToString实现中,如何利用decimal.ToString(string, IFormatProvider)的现有格式化功能而不干扰我自己?看起来这将是相当混乱的,它导致我倾向于更有限的解决方案,只是定义“G”(两个或三个小数位,没有尾随空格)和“S”(与“G”相同,但我的Price结构的格式。如果需要,可以使用尾随空格。

有谁能告诉我是否有办法让我做这种自定义格式化功能而不会有太多麻烦?

2 个答案:

答案 0 :(得分:1)

有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.iformatprovider.aspx

// "01.13 " or "01.13". Standard formatting applied: $123.45
// "01.315" or "01.31½". Standard formatting applied: $123.45

public class Test
{
    void Main()
    {
        decimal number1 = 1.13M;
        decimal number2 = 1.315M;

        string output1 = String.Format(new CustomNumberFormat(),
                                 "\"{0:G}\" or \"{0:S}\". Standard formatting applied: {1:C2}",
                                 number1, 123.45);
        Console.WriteLine(output1);

        string output2 = String.Format(new CustomNumberFormat(),
                                 "\"{0:G}\" or \"{0:S}\". Standard formatting applied: {1:C2}",
                                 number2, 123.45);
        Console.WriteLine(output2);
    }
}

public class CustomNumberFormat : System.IFormatProvider, System.ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return this;
        else
            return null;
    }

    public string Format(string fmt, object arg, System.IFormatProvider formatProvider)
    {
        // Provide default formatting if arg is not a decimal. 
        if (arg.GetType() != typeof(decimal))
            try
            {
                return HandleOtherFormats(fmt, arg);
            }
            catch (FormatException e)
            {
                throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e);
            }

        // Provide default formatting for unsupported format strings. 
        string ufmt = fmt.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
        if (!(ufmt == "G" || ufmt == "S"))
            try
            {
                return HandleOtherFormats(fmt, arg);
            }
            catch (FormatException e)
            {
                throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e);
            }

        // Convert argument to a string. 
        string result = ((decimal)arg).ToString("0#.000");

        if (ufmt == "G")
        {
            var lastFormattedChar = result[result.Length - 1];
            switch (lastFormattedChar)
            {
                case '0':
                    result = result.Substring(0, result.Length - 1) + " ";
                    break;
            }

            return result;
        }
        else if (ufmt == "S")
        {
            var lastFormattedChar = result[result.Length - 1];
            switch (lastFormattedChar)
            {
                case '0':
                    result = result.Substring(0, result.Length - 1);
                    break;
                case '5':
                    result = result.Substring(0, result.Length - 1) + "½";
                    break;
            }

            return result;
        }
        else
        {
            return result;
        }
    }

    private string HandleOtherFormats(string format, object arg)
    {
        if (arg is System.IFormattable)
            return ((System.IFormattable)arg).ToString(format, System.Globalization.CultureInfo.CurrentCulture);
        else if (arg != null)
            return arg.ToString();
        else
            return String.Empty;
    }
}

答案 1 :(得分:0)

尝试将格式提供程序作为parameter实现中的IValueConverter.Convert参数传递:

<TextBlock Text="{Binding Value, Mode=OneWay, Converter={StaticResource PriceConverter}, ConverterParameter=#0.00F"/>

然后,在转换器内:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    string formatString = parameter as string;

    if(formatString != null)
    {
        // Your code here
    }
    else
    {
        // Whatever you want to do here
    }

}