不带舍入的 WPF 十进制自定义格式
WPF Decimal Custom Format without Rounding
MS 文档说这是不可能的...希望我在文档中遗漏了什么。
想要将十进制数格式化为:-9,999.000
使用 StringFormat={}{0:#,0.000}
有效,但这会在我想“截断”输入的地方“舍入”输入。
为了测试,在 WPF 表单中将此字符串格式应用到绑定到小数 属性.
的 TextBox
当我输入测试值时,实际格式化值与预期格式化值不匹配,因为格式字符串会自动“四舍五入”小数值。
Enter Expected Actual
1111.999 1,111.999 1,111.999
1111.9999 1,111.999 1,112,000
MS 文档明确指出“0”占位符将始终四舍五入。
很好,但我不想要这种行为。
问题:是否有一种格式字符串可以在不使用“0”的情况下显示 3 位小数(尾随零)?或者是否有格式字符串在小数点后 3 位截断而不是在小数点后 3 位舍入?
我确实尝试了 StringFormat={}{0:#,0.###}
,但这会删除尾随零。 (还有 StringFormat={}{F3}
)。
Enter Expected Actual
1111.100 1,111.100 1,111.1
我能找到的最接近的解决方案是 [ none 的答案确实涉及舍入与截断。
更新
xaml 使用 StringFormat 的代码。
<TextBox Text="{Binding
TemplateNumber,
StringFormat={}{0:F3},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged,
Delay=500}"
/>
更新 2
将 IValueConverter 函数转换为截断而不是舍入...
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return string.Empty;
int decimalPlaces = 3; // HACK: hardcoded, needs to be parameter
// decimal decimalShift = (decimal)Math.Pow(10, decimalPlaces);
decimal decimalValue = (decimal)value;
// decimal decimalWhole = Math.Truncate(decimalValue);
// decimal decimalFraction = Math.Truncate((decimalValue - decimalWhole) * decimalShift);
string format;
string result;
// simpler truncation approach
decimal d = Math.Round(decimalValue, decimalPlaces, MidpointRounding.ToZero);
format = "{0:N" + decimalPlaces.ToString() + "}";
result = string.Format(format, d);
/*
if (decimalPlaces > 0)
{
format = "{0:N0}.{1:" + new string('0', decimalPlaces) + "}";
result = string.Format(format, decimalWhole, decimalFraction);
}
else // whole number only
{
format = "{0:N0}";
result = string.Format(format, decimalWhole);
}
*/
return result;
}
更新 3
经过进一步测试...“显示值”现在是正确的,但“绑定 属性”不是。
下面 xaml 代码中的 属性 TemplateNumber
具有输入到文本框中的值,而不是文本框显示的值(由 IValueConverter 修改。
<TextBox
Text="{Binding TemplateNumber,
Converter={converters:DecimalConverter Precision=4},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged,
Delay=500}"
/>
即:
- 输入值:1.99999(5位精度)
- 显示值:1.9999(4位精度,来自IValueConverter)
- TemplateNumber值:1.99999(5位精度,与输入值不变)
也需要修复转换回来的舍入....
public object ConvertBack(object value, Type targetType, object
parameter, CultureInfo culture)
{
// note: remove any extra white space
if (decimal.TryParse(value?.ToString().Replace(" ",""), out decimal d))
{
int decimalPlaces = Precision ?? 0;
decimal result = Math.Round(d, decimalPlaces, MidpointRounding.ToZero);
return result;
}
return null; // type is decimal? so null is legit
}
更新 4
添加精度作为 属性 以删除硬编码值。
使用 MarkupExtension 方法。添加起来真的很简单。不知道如何添加多个属性(xaml barfs:未来要解决的问题)。
仅供参考,用于添加精度的 MarkupExtension。
public class DecimalConverter : MarkupExtension, IValueConverter
{
#region Markup Extension
public int? Precision { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#endregion // markup extension
...
xaml 与更新 3 一致。
.Net custom numerical formatting 是一成不变的,您对此无能为力。具体来说:
The "00" specifier causes the value to be rounded to the nearest digit preceding the decimal, where rounding away from zero is always used
如果你想应用不同类型的舍入,你可以使用 Math.Round
,它带有一个参数指定你想要的舍入类型(在你的情况下,它看起来像 MidpointRounding.ToZero
或 MidpointRounding.ToNegativeInfinity
,很难说)。
您可以使用 IValueConverter 例如:
class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0:F3}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (decimal.TryParse(value?.ToString().Replace(" ", ""), out decimal num1))
return num1;
return null;
}
}
将其添加到您的控件中
<UserControl.Resources>
<local:DecimalConverter x:Key="MyDecimalConverter"/>
</UserControl.Resources>
在文本框中使用
<TextBox Text={Binding ...., Converter={StaticResource MyDecimalConverter}}/>
MS 文档说这是不可能的...希望我在文档中遗漏了什么。
想要将十进制数格式化为:-9,999.000
使用 StringFormat={}{0:#,0.000}
有效,但这会在我想“截断”输入的地方“舍入”输入。
为了测试,在 WPF 表单中将此字符串格式应用到绑定到小数 属性.
的 TextBox当我输入测试值时,实际格式化值与预期格式化值不匹配,因为格式字符串会自动“四舍五入”小数值。
Enter Expected Actual
1111.999 1,111.999 1,111.999
1111.9999 1,111.999 1,112,000
MS 文档明确指出“0”占位符将始终四舍五入。 很好,但我不想要这种行为。
问题:是否有一种格式字符串可以在不使用“0”的情况下显示 3 位小数(尾随零)?或者是否有格式字符串在小数点后 3 位截断而不是在小数点后 3 位舍入?
我确实尝试了 StringFormat={}{0:#,0.###}
,但这会删除尾随零。 (还有 StringFormat={}{F3}
)。
Enter Expected Actual
1111.100 1,111.100 1,111.1
我能找到的最接近的解决方案是 [ none 的答案确实涉及舍入与截断。
更新
xaml 使用 StringFormat 的代码。
<TextBox Text="{Binding
TemplateNumber,
StringFormat={}{0:F3},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged,
Delay=500}"
/>
更新 2
将 IValueConverter 函数转换为截断而不是舍入...
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return string.Empty;
int decimalPlaces = 3; // HACK: hardcoded, needs to be parameter
// decimal decimalShift = (decimal)Math.Pow(10, decimalPlaces);
decimal decimalValue = (decimal)value;
// decimal decimalWhole = Math.Truncate(decimalValue);
// decimal decimalFraction = Math.Truncate((decimalValue - decimalWhole) * decimalShift);
string format;
string result;
// simpler truncation approach
decimal d = Math.Round(decimalValue, decimalPlaces, MidpointRounding.ToZero);
format = "{0:N" + decimalPlaces.ToString() + "}";
result = string.Format(format, d);
/*
if (decimalPlaces > 0)
{
format = "{0:N0}.{1:" + new string('0', decimalPlaces) + "}";
result = string.Format(format, decimalWhole, decimalFraction);
}
else // whole number only
{
format = "{0:N0}";
result = string.Format(format, decimalWhole);
}
*/
return result;
}
更新 3
经过进一步测试...“显示值”现在是正确的,但“绑定 属性”不是。
下面 xaml 代码中的 属性 TemplateNumber
具有输入到文本框中的值,而不是文本框显示的值(由 IValueConverter 修改。
<TextBox
Text="{Binding TemplateNumber,
Converter={converters:DecimalConverter Precision=4},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged,
Delay=500}"
/>
即:
- 输入值:1.99999(5位精度)
- 显示值:1.9999(4位精度,来自IValueConverter)
- TemplateNumber值:1.99999(5位精度,与输入值不变)
也需要修复转换回来的舍入....
public object ConvertBack(object value, Type targetType, object
parameter, CultureInfo culture)
{
// note: remove any extra white space
if (decimal.TryParse(value?.ToString().Replace(" ",""), out decimal d))
{
int decimalPlaces = Precision ?? 0;
decimal result = Math.Round(d, decimalPlaces, MidpointRounding.ToZero);
return result;
}
return null; // type is decimal? so null is legit
}
更新 4
添加精度作为 属性 以删除硬编码值。 使用 MarkupExtension 方法。添加起来真的很简单。不知道如何添加多个属性(xaml barfs:未来要解决的问题)。
仅供参考,用于添加精度的 MarkupExtension。
public class DecimalConverter : MarkupExtension, IValueConverter
{
#region Markup Extension
public int? Precision { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#endregion // markup extension
...
xaml 与更新 3 一致。
.Net custom numerical formatting 是一成不变的,您对此无能为力。具体来说:
The "00" specifier causes the value to be rounded to the nearest digit preceding the decimal, where rounding away from zero is always used
如果你想应用不同类型的舍入,你可以使用 Math.Round
,它带有一个参数指定你想要的舍入类型(在你的情况下,它看起来像 MidpointRounding.ToZero
或 MidpointRounding.ToNegativeInfinity
,很难说)。
您可以使用 IValueConverter 例如:
class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0:F3}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (decimal.TryParse(value?.ToString().Replace(" ", ""), out decimal num1))
return num1;
return null;
}
}
将其添加到您的控件中
<UserControl.Resources>
<local:DecimalConverter x:Key="MyDecimalConverter"/>
</UserControl.Resources>
在文本框中使用
<TextBox Text={Binding ...., Converter={StaticResource MyDecimalConverter}}/>