通过验证绑定到双字段

时间:2022-12-22 16:04:28

I'm trying to bind TextBox to double property of some object with UpdateSourceTrigger=PropertyChanged. The goal is to immediately during editing validate entered value to be in allowed range (and display an error if not). I want to implement validation on Model level, i.e. via IDataErrorInfo.

我正在尝试使用UpdateSourceTrigger = PropertyChanged将TextBox绑定到某个对象的double属性。目标是在编辑期间立即验证输入的值是否在允许的范围内(如果没有则显示错误)。我想在Model级别实现验证,即通过IDataErrorInfo。

All works great when I bind to int property, but if property is double then a frustrating editing behavior appears: after erasing last significant digit in fractional part of number - the decimal separator is automatically erased (with all possible fractional zeroes). For example, after erasing digit '3' from number '12.03' the text is changed to '12' instead of '12.0'.

当我绑定到int属性时,所有工作都很好,但如果属性为double,则会出现令人沮丧的编辑行为:在删除数字的小数部分中的最后一个有效数字后 - 小数分隔符将自动擦除(包含所有可能的小数零)。例如,在从数字'12 .03'中删除数字'3'后,文本将变为'12'而不是'12 .0'。

Please, help.

请帮忙。

Here is the sample code:

以下是示例代码:

MainWindow.xaml:

MainWindow.xaml:

<Window x:Class="BindWithValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="80" Width="200" WindowStartupLocation="CenterOwner">

  <StackPanel>
    <TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
      <TextBox.Style>
        <Style TargetType="TextBox">
          <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
              <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </TextBox.Style>
    </TextBox>
  </StackPanel>
</Window>

MainWindow.xaml.cs:

MainWindow.xaml.cs:

namespace BindWithValidation
{
  public partial class MainWindow : Window
  {
    private UISimpleData _uiData = new UISimpleData();

    public MainWindow()
    {
      InitializeComponent();
      DataContext = _uiData;
    }
  }
}

UISimpleData.cs:

UISimpleData.cs:

namespace BindWithValidation
{
  public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
  {
    private double _doubleField = 12.03;

    public double DoubleField
    {
      get
      {
        return _doubleField;
      }
      set
      {
        if (_doubleField == value)
          return;

        _doubleField = value;
        RaisePropertyChanged("DoubleField");
      }
    }

    public string this[string propertyName]
    {
      get
      {
        string validationResult = null;
        switch (propertyName)
        {
          case "DoubleField":
          {
            if (DoubleField < 2 || DoubleField > 5)
              validationResult = "DoubleField is out of range";
            break;
          }

          default:
            throw new ApplicationException("Unknown Property being validated on UIData");
        }

        return validationResult;
      }
    }

    public string Error { get { return "not implemented"; } }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string property)
    {
      if ( PropertyChanged != null )
        PropertyChanged(this, new PropertyChangedEventArgs(property)); 
    }
  }
}

6 个解决方案

#1


10  

I realize I'm a little late to the party but I found a (I think) rather clean solution to this problem.

我意识到我有点迟到了,但我找到了一个(我认为)相当干净的解决方案来解决这个问题。

A clever converter that remembers the last string converted to double and returns that if it exists should do everything you want.

一个聪明的转换器,它记住转换为double的最后一个字符串并返回它(如果它存在)应该做你想要的一切。

Note that when the user changes the contents of the textbox, ConvertBack will store the string the user input, parse the string for a double, and pass that value to the view model. Immediately after, Convert is called to display the newly changed value. At this point, the stored string is not null and will be returned.

请注意,当用户更改文本框的内容时,ConvertBack将存储用户输入的字符串,解析字符串以获取double,并将该值传递给视图模型。紧接着,调用Convert以显示新更改的值。此时,存储的字符串不为null,将返回。

If the application instead of the user causes the double to change only Convert is called. This means that the cached string will be null and a standard ToString() will be called on the double.

如果应用程序而不是用户导致double更改,则仅调用Convert。这意味着缓存的字符串将为null,并且将在double上调用标准ToString()。

In this way, the user avoids strange surprises when modifying the contents of the textbox but the application can still trigger a change.

通过这种方式,用户在修改文本框的内容时避免了奇怪的意外,但应用程序仍然可以触发更改。

public class DoubleToPersistantStringConverter : IValueConverter
{
    private string lastConvertBackString;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is double)) return null;

        var stringValue = lastConvertBackString ?? value.ToString();
        lastConvertBackString = null;

        return stringValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is string)) return null;

        double result;
        if (double.TryParse((string)value, out result))
        {
            lastConvertBackString = (string)value;
            return result;
        }

        return null;
    }
}

#2


6  

The behavior of binding float values to a textbox has been changed from .NET 4 to 4.5. With .NET 4.5 it is no longer possible to enter a separator character (comma or dot) with ‘UpdateSourceTrigger = PropertyChanged’ by default.

将浮点值绑定到文本框的行为已从.NET 4更改为4.5。使用.NET 4.5,默认情况下不再可以使用“UpdateSourceTrigger = PropertyChanged”输入分隔符(逗号或点)。

Microsoft says, this (is) intended

微软表示,这是有意的

If you still want to use ‘UpdateSourceTrigger = PropertyChanged’, you can force the .NET 4 behavior in your .NET 4.5 application by adding the following line of code to the constructor of your App.xaml.cs:

如果您仍想使用“UpdateSourceTrigger = PropertyChanged”,则可以通过将以下代码行添加到App.xaml.cs的构造函数中来强制.NET 4.5应用程序中的.NET 4行为:

public App()  
{
    System.Windows.FrameworkCompatibilityPreferences
               .KeepTextBoxDisplaySynchronizedWithTextProperty = false;   
}

(Sebastian Lux - Copied verbatim from here)

(塞巴斯蒂安·勒克斯 - 从这里逐字复制)

#3


4  

Tried formatting the value with decimal places?

尝试用小数位格式化值?

It may be weird though since you will then always have N decimal places.

这可能很奇怪,因为你将总是有N个小数位。

<TextBox.Text>
    <Binding Path="DoubleField" StringFormat="{}{0:0.00}" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"/>
</TextBox.Text>

If having fixed decimal places is not good enough, you may have to write a converter that treats the value as a string and converts it back to a double.

如果固定的小数位数不够好,您可能必须编写一个转换器,将值视为字符串并将其转换回double。

#4


4  

The problem is that you are updating your property every time the value changes. When you change 12.03 to 12.0 it is rounded to 12.

问题是每次值更改时都要更新属性。当您将12.03更改为12.0时,它将舍入为12。

You can see changes by providing delay by changing the TextBox in xaml like this

您可以通过像这样更改xaml中的T​​extBox来提供延迟来查看更改

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,Delay=500, ValidatesOnDataErrors=True}">

but delay will notify and set the property after the delay time in mili sec. Better use StringFormat like this

但延迟将在mili sec的延迟时间之后通知并设置属性。最好像这样使用StringFormat

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,StringFormat=N2, ValidatesOnDataErrors=True}">

#5


2  

I have run into the same problem, and have found a quite simple solution: use a custom validator, which does not return "valid" when the Text ends in "." or "0":

我遇到了同样的问题,并找到了一个非常简单的解决方案:使用自定义验证器,当文本以“。”结尾时,它不会返回“有效”。或“0”:

double val = 0;
string tmp = value.ToString();

if (tmp.EndsWith(",") || tmp.EndsWith("0") || tmp.EndsWith("."))
{
    return new ValidationResult(false, "Enter another digit, or delete the last one.");
}
else
{
    return ValidationResult.ValidResult;
}

#6


1  

Try using StringFormat on your binding:

尝试在绑定上使用StringFormat:

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat='0.0'}"> 

Not sure if that string format is even right since I've not done one for a while but it's just an example

不确定那个字符串格式是否正确,因为我暂时没有做过,但这只是一个例子

#1


10  

I realize I'm a little late to the party but I found a (I think) rather clean solution to this problem.

我意识到我有点迟到了,但我找到了一个(我认为)相当干净的解决方案来解决这个问题。

A clever converter that remembers the last string converted to double and returns that if it exists should do everything you want.

一个聪明的转换器,它记住转换为double的最后一个字符串并返回它(如果它存在)应该做你想要的一切。

Note that when the user changes the contents of the textbox, ConvertBack will store the string the user input, parse the string for a double, and pass that value to the view model. Immediately after, Convert is called to display the newly changed value. At this point, the stored string is not null and will be returned.

请注意,当用户更改文本框的内容时,ConvertBack将存储用户输入的字符串,解析字符串以获取double,并将该值传递给视图模型。紧接着,调用Convert以显示新更改的值。此时,存储的字符串不为null,将返回。

If the application instead of the user causes the double to change only Convert is called. This means that the cached string will be null and a standard ToString() will be called on the double.

如果应用程序而不是用户导致double更改,则仅调用Convert。这意味着缓存的字符串将为null,并且将在double上调用标准ToString()。

In this way, the user avoids strange surprises when modifying the contents of the textbox but the application can still trigger a change.

通过这种方式,用户在修改文本框的内容时避免了奇怪的意外,但应用程序仍然可以触发更改。

public class DoubleToPersistantStringConverter : IValueConverter
{
    private string lastConvertBackString;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is double)) return null;

        var stringValue = lastConvertBackString ?? value.ToString();
        lastConvertBackString = null;

        return stringValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is string)) return null;

        double result;
        if (double.TryParse((string)value, out result))
        {
            lastConvertBackString = (string)value;
            return result;
        }

        return null;
    }
}

#2


6  

The behavior of binding float values to a textbox has been changed from .NET 4 to 4.5. With .NET 4.5 it is no longer possible to enter a separator character (comma or dot) with ‘UpdateSourceTrigger = PropertyChanged’ by default.

将浮点值绑定到文本框的行为已从.NET 4更改为4.5。使用.NET 4.5,默认情况下不再可以使用“UpdateSourceTrigger = PropertyChanged”输入分隔符(逗号或点)。

Microsoft says, this (is) intended

微软表示,这是有意的

If you still want to use ‘UpdateSourceTrigger = PropertyChanged’, you can force the .NET 4 behavior in your .NET 4.5 application by adding the following line of code to the constructor of your App.xaml.cs:

如果您仍想使用“UpdateSourceTrigger = PropertyChanged”,则可以通过将以下代码行添加到App.xaml.cs的构造函数中来强制.NET 4.5应用程序中的.NET 4行为:

public App()  
{
    System.Windows.FrameworkCompatibilityPreferences
               .KeepTextBoxDisplaySynchronizedWithTextProperty = false;   
}

(Sebastian Lux - Copied verbatim from here)

(塞巴斯蒂安·勒克斯 - 从这里逐字复制)

#3


4  

Tried formatting the value with decimal places?

尝试用小数位格式化值?

It may be weird though since you will then always have N decimal places.

这可能很奇怪,因为你将总是有N个小数位。

<TextBox.Text>
    <Binding Path="DoubleField" StringFormat="{}{0:0.00}" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"/>
</TextBox.Text>

If having fixed decimal places is not good enough, you may have to write a converter that treats the value as a string and converts it back to a double.

如果固定的小数位数不够好,您可能必须编写一个转换器,将值视为字符串并将其转换回double。

#4


4  

The problem is that you are updating your property every time the value changes. When you change 12.03 to 12.0 it is rounded to 12.

问题是每次值更改时都要更新属性。当您将12.03更改为12.0时,它将舍入为12。

You can see changes by providing delay by changing the TextBox in xaml like this

您可以通过像这样更改xaml中的T​​extBox来提供延迟来查看更改

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,Delay=500, ValidatesOnDataErrors=True}">

but delay will notify and set the property after the delay time in mili sec. Better use StringFormat like this

但延迟将在mili sec的延迟时间之后通知并设置属性。最好像这样使用StringFormat

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,StringFormat=N2, ValidatesOnDataErrors=True}">

#5


2  

I have run into the same problem, and have found a quite simple solution: use a custom validator, which does not return "valid" when the Text ends in "." or "0":

我遇到了同样的问题,并找到了一个非常简单的解决方案:使用自定义验证器,当文本以“。”结尾时,它不会返回“有效”。或“0”:

double val = 0;
string tmp = value.ToString();

if (tmp.EndsWith(",") || tmp.EndsWith("0") || tmp.EndsWith("."))
{
    return new ValidationResult(false, "Enter another digit, or delete the last one.");
}
else
{
    return ValidationResult.ValidResult;
}

#6


1  

Try using StringFormat on your binding:

尝试在绑定上使用StringFormat:

<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat='0.0'}"> 

Not sure if that string format is even right since I've not done one for a while but it's just an example

不确定那个字符串格式是否正确,因为我暂时没有做过,但这只是一个例子