下面是我的代码:

using System.Windows.Controls;

namespace MyTest.validations
{
    public class DecimalValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            decimal valueParsed;
            string? valueString = value.ToString();
            if (decimal.TryParse(valueString, out valueParsed))
            {
                return new ValidationResult(true, null);
            }

            return new ValidationResult(false, "Inserisci un valore Decimale corretto");
        }
    }
}

如果对象的value(由WPF's TextBox填充)其1.0.1(用调试器判断),则函数TryParse返回True(不是小数).

为什么?如何正确判断值是否为decimal

编辑:使用@Panagiotis Kanavos的建议,我遇到的问题是写1,0(例如),它变成了10,而不是1,0.

这是我的View Model Data类:

using System.ComponentModel;

namespace MyTest.models
{
    public class ViewModelData : INotifyPropertyChanged
    {
        private decimal? valoreDecimal;
        public decimal? ValoreDecimal
        {
            get { return valoreDecimal; }
            set
            {
                valoreDecimal = value + 0.0m;
                OnPropertyChanged(nameof(ValoreDecimal));
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

这就是XML:

<TextBox 
    x:Name="ValoreDecimal"
    Width="130" 
    Height="40"
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    Margin="22,70,0,0" 
    Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="ValoreDecimal">
            <Binding.ValidationRules>
                <validators:DecimalValidationRule ValidationStep="RawProposedValue"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <Validation.ErrorTemplate>
        <ControlTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <AdornedElementPlaceholder x:Name="textBox" Grid.Column="0"/>
                <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red" Grid.Column="1" Margin="5,0,0,0"/>
            </Grid>
        </ControlTemplate>
    </Validation.ErrorTemplate>
</TextBox>

一旦写下1,0,现在就变成1,0.为什么?

推荐答案

您的问题由两个不同但相关的问题组成.

Problem 1: the explicit conversion using e.g. decimal.Parse (takes place in your ValidationRule and while this particular conversion is correct, it doesn't seem to satisfy your special requirements).
Problem 2: the implicit type conversion between XAML and C#, that is performed by the XAML engine (takes place while binding TextBox.Text to ViewModelData.ValoreDecimal).

这两个问题都是相关的,因为它们将组分隔符误解为小数分隔符.区别在于,一个是由显式客户端代码转换产生的,而另一个是由"隐式"框架转换执行的.

正如其他用户已经指出的那样,您正在观察一个文化问题,在某些文化中,小数分隔符是.符号,而在其他文化中,小数分隔符是,符号.当然,小数符号与数字相关.

另一方面,组分隔符与数字无关,因为它只是一种符号惯例,旨在提高巨额数字的可读性.

现在,编译器本身不使用组分隔符存储数字.例如,以下代码将无法编译:

// Does not compile
decimal value = 100,000.58;

Group separators are only possible on UI level number presentation i.e. as string representation of a number.
And because group separators are not relevant and not presentable as language primitive, they simply get stripped when a numeric string is converted to a numeric primitive like double or decimal.

例如: 当 .是组分隔符,,是小数分隔符 然后 "12.500"等于12500或12500,0

,是组分隔符,.是小数分隔符 然后 "1,0"等于10或10.0

它只是一个group separator,没有数字相关性,而且根本不是由计算机表示的

我将首先修复问题#2,因为修复也会影响问题#1的解决方案.

Fix the implicit XAML type conversion (problem #2)

The XAML engine will use a CultureInfo object to perform the implicit type conversions. The default XAML culture is "en-US".
Now here is the caveat: the implicit XAML conversions will fail to produce correct results when the user's system culture is not "en-US".

假设您的数字值是来自UI的输入,并且用户当前的系统文化不是en-US(这不是很不可能),那么您就遇到了问题.

例如,当将类型stringTextBox.Text绑定到类型decimalNumerValue属性时

<TextBox Text="{Binding NumberValue}" />
private decimal? numberValue;
public decimal? NumberValue
{
  get => this.numberValue; 
  set
  {
    this.numberValue = value;
    OnPropertyChanged(nameof(this.NumberValue));
  }
}

然后XML引擎执行从stringdecimal的隐式转换. 现在,当用户将"1,0"输入TextBox时,XML引擎将将string转换为10(只是删除组分隔符)-这不是正确的值.

This is because the XAML engine will use the FrameworkElement.Language property value to perform all implicit type conversions e.g., from double to string (for example when binding a string property to a double or like in your case a decimal property).
The default of the FrameworkElement.Language property is the "en-US" language.
That's also the reason why the CultureInfo parameter that is passed by the XAML engine to the IValueConverter or ValidationRule is always the "en-US" culture.
However, this little detail will break the numeric conversion (or alphanumeric in general as text e.g. string comparison is also affected) when the application runs on systems that are not using "en-US" as culture.

这意味着XML引擎使用错误的(对于非美国区域性)CultureInfo来转换输入.XML引擎本质上总是用"en-US"文化信息调用Convert.ToDecimal:

// Returns 10
string value = Convert.ToDecimal("1,0", CultureInfo.GetCultureInfo("en-US"));

同样,由于使用了"en-US"培养物,小数分隔符被解释为组分隔符.因此,组分隔符从输入中被剥离,从而产生一个整元.

您目前的年龄为trying,可以通过将0.0m添加到值来修复它,目标是将整值转换回小数值.它不会起作用,因为它会简单地将10转换为10.0,但这仍然不是1.0,因此是错误的.

建议修复

The most convenient solution is to configure the XAML engine to always use the correct default CultureInfo.
We can achieve this by overriding the default value of the FrameworkElement.Language dependency property. We can change default values of dependency properties by overriding the default property metadata.

重写属性元数据的好地方是WPF应用程序的入口点App类:

App.xaml.cs

public partial class App : Application
{
  static App()
  {
    // Set the WPF default CultureInfo to the system's current culture
    FrameworkElement.LanguageProperty.OverrideMetadata(
      typeof(FrameworkElement),
      new FrameworkPropertyMetadata(
        defaultValue: XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
  }
}

现在,框架将始终使用正确的CultureInfo.您现在可以依赖IValueConverter.ConvertValidationRule.Validate等方法的CultureInfo个参数.

最后,从ViewModelData.ValoreDecimal属性的属性设置器中删除value + 0.0m:

ViewModelData.cs

private decimal? valoreDecimal;
public decimal? ValoreDecimal
{
  get => this.valoreDecimal; 
  set
  {
    this.valoreDecimal = value;
    OnPropertyChanged(nameof(this.ValoreDecimal));
  }
}

Fix the input validation (problem #1)

默认情况下,decimal.Parse和类似的转换器将正确使用当前区域性进行转换.decimal.Parse将在内部使用NumberFormatInfo.CurrentInfo,相当于CultureInfo.CurrentCulture.NumberFormatInfo.所以,解析本身是正确的.如前所述,解析器正确地将.识别为您的区域性的组分隔符,并将1.0.1转换为101.

"if object's value (filled by a WPF's TextBox) its 1.0.1 (checked with debugger), the function TryParse return true (which is not a decimal). Why? How can I correctly check if the value is decimal?"

然而,您显然不满意101次传递作为小数(尽管对于编译器来说是如此,并且就数字学而言,一般来说,1 = 1.0).此时,您的要求还不清楚,因为您没有清楚地解释为什么输入在您的情况下不被认为有效.

建议修复

如果您要求用户输入小数分隔符,则

  • 考虑将其隐式附加到TextBox.Text值,例如,通过使用Binding.StringFormat(推荐)
  • 或手动解析字符串以识别缺席(并强制用户添加例如.0)

以下示例不允许"1.0.1"´ or "1,0,1". On the other hand, ". On the other hand, ,0"'等输入有效:

public class DecimalValidationRule : ValidationRule
{
  public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
  {
    // Because ValoreDecimal is explicitly defined as nullable 
    // I assume that NULL is treated as an anticipated and valid value
    if (value is null)
    {
      return ValidationResult.ValidResult;
    }

    string valueString = value.ToString();

    // Because we have previously configured the framework's CultureInfo to be 
    // CultureInfo.CurrentCulture we can safely use 
    // the 'cultureInfo' parameter of the current method
    string[] valueSegments = valueString.Split(cultureInfo.NumberFormat.NumberDecimalSeparator, StringSplitOptions.RemoveEmptyEntries);
    bool hasZeroOrMultipleDeciamlSeparators = 
      valueSegments.Length == 1 || valueSegments.Length > 2;
    if (hasZeroOrMultipleDeciamlSeparators)
    {
      return new ValidationResult(false, "Inserisci un valore Decimale corretto");
    }

    // TryParse will naturally use the correct CultureInfo.CurrentCulture to parse the string. 
    // Nothing wrong here.
    return double.TryParse(valueString, out _)
      ? ValidationResult.ValidResult
      : new ValidationResult(false, "Inserisci un valore Decimale corretto");
  }
}

如果您想阻止用户使用组分隔符(例如,为了避免小数分隔符混淆),然后手动解析字符串

bool hasGroupSeparators = valueString.Contains(cultureInfo.NumberFormat.NumberGroupSeparator);

或者通过使用允许传递NumberStyles个标志的过载来配置值解析器.

以下示例不允许分组数字输入,例如"1.0.1"':

public class DecimalValidationRule : ValidationRule
{
  public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
  {
    // Because ValoreDecimal is explicitly defined as nullable 
    // I assume that NULL is treated as an anticipated and valid value
    if (value is null)
    {
      return ValidationResult.ValidResult;
    }

    string valueString = value.ToString();

    // TryParse will naturally use the correct CultureInfo.CurrentCulture to parse the string.
    // However, because of the previous fix of the framework language
    // we can safely use the cultureInfo parameter.
    return double.TryParse(valueString, NumberStyles.Float, cultureInfo, out _)
      ? ValidationResult.ValidResult
      : new ValidationResult(false, "Inserisci un valore Decimale corretto");
  }
}

或者也许您想结合这两种条件.

言论

The valoreDecimal = value + 0.0m; in the ViewModelData.ValoreDecimal property is not very nice. It's programmatically not relevant. The operation is only to format the value for the presentation in the view.
For this reason, I would format the value in the view e.g., by using Binding.StringFormat.

如果提供的值没有小数位,则应用自定义格式说明符(例如"0.0######")将附加值为0的小数位.您可以使用其他说明符,例如四舍五入到小数点后三位:

<TextBox x:Name="ValoreDecimal">
  <TextBox.Text>
    <Binding Path="ValoreDecimal" 
             StringFormat="{}{0:0.0##########}">
      <Binding.ValidationRules>
        <validators:DecimalValidationRule ValidationStep="RawProposedValue"/>
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>

  <Validation.ErrorTemplate>
    ...     
  </Validation.ErrorTemplate>
</TextBox>

You can also bind the TextBox to a string property. Then validate this property by implementing INotifyDataErrorInfo and if valid convert the value to a decimal and store in a decimal property or field that you use internally (outside the view).
How to add validation to view model properties or how to implement INotifyDataErrorInfo
This way you would eliminate 1) the binding validation and 2) the implicit XAML to C# conversion. You would have a clean separation between view presentation and data logic.

另一个非常干净的解决方案是创建一个自定义的Numeric TextBox类,它扩展了TextBox,并且可以优雅地实现表示规则和数字输入规则(当然,不需要业务逻辑相关的验证).与INotifyDataErrorInfo一样,该解决方案将清理您的XML并显着提高可重用性和可维护性.

Csharp相关问答推荐

如何从C#中有类.x和类.y的类列表中映射List(字符串x,字符串y)?

为什么我在PuppeteerSharp中运行StealthPlugin时会出现错误?

当打印一行x个项目时,如何打印最后一行项目?

更改对象的旋转方向

C#类主构造函数中的调试参数

如何在Visual Studio代码中更改大括号模式{},用于C#语言

无法解析数据库上下文的服务

C#EF Core 8.0表现与预期不符

S能够用DATETIME来计算,这有什么错呢?

Thad.Sept()vs Task.Delay().Wait()

持有者安全定义未显示在Swagger.NET 8中

如何在我的C#应用程序中设置带有reactjs前端的SignalR服务器?

无法使用[FromForm]发送带有图像和JSON的多部分请求

在C#中有没有办法减少大型数组中新字符串的分配?

Foreach非常慢的C#

如何在使用Google.Drive.apis.V3下载文件/文件夹之前压缩?

从HTML元素获取 colored颜色

并发表更新.EF核心交易

Cmd中的&ping.end()";有时会失败,而";ping";总是有效

自定义ConsoleForMatter中的DI/Http上下文