我对 decimal
值有一些相当尴尬的格式要求。简而言之:显示到两位小数并带有尾随空格,除非第三位小数是 5,在这种情况下显示到三位小数。
这种格式也需要相当灵活。具体来说,并不总是需要尾随空格,当第三位小数为“5”时,可能首选“½”。
例子:
我需要在其他不相关的 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();
}
}
我现在正试图将其提取到一个更基本的构建 block 中,我可以在整个 UI 层中使用它。我最初的想法是一个自定义格式提供程序,然后我可以从 Binding
中使用它:
<TextBlock Text="{Binding Value, FormatString=WHATEVER}"/>
这个想法是,格式字符串可能类似于“#0.005”,表示如果它是 5,则仅显示第三个小数位,或者“#0.00F”,它试图将第三个小数表示为分数。但是,我无法从绑定(bind)中找到使用特定格式提供程序的方法,这对我来说似乎是一个主要限制,但也许我遗漏了什么......?
经过更多的实验和调查,我得出的结论是,我唯一的选择是定义自己的类型:
public struct Price : IFormattable
这种类型将封装我需要的额外格式化功能。但是,现在我遇到了另一个难题:在我的 ToString
实现中,如何利用现有的 decimal.ToString(string, IFormatProvider)
格式化功能而不干扰我自己的?看起来这会非常困惑,这导致我倾向于只定义“G”(两位或三位小数,没有尾随空格)和“S”(与“G”相同,但我的 Price
结构的格式。
谁能告诉我是否有办法让我轻松完成这种自定义格式化功能?
最佳答案
见 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;
}
}
https://stackoverflow.com/questions/20184361/