不带舍入的 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}"
  />

即:

也需要修复转换回来的舍入....

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.ToZeroMidpointRounding.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}}/>