C# 为什么文本框的值会重置为以前的值,而不是显示错误?

C# 为什么文本框的值会重置为以前的值,而不是显示错误?,c#,wpf,validation,multibinding,C#,Wpf,Validation,Multibinding,我有一个示例,其中我使用一些TextBox控件绑定视图模型的属性,包括验证规则。在大多数情况下,这很好。但是,当我尝试包含绑定的文本框的IsFocused属性时,在控件中输入无效数字的情况下遇到了问题 当我在直接绑定到视图模型属性的文本框控件中输入错误的数字时,错误将按预期显示(文本框周围的红色边框)。但是在与包含查看模型属性和文本框的多重绑定绑定的文本框中,不会显示错误,并且该值会重置为以前的有效值 例如,如果小于10的数字无效,并且我输入了3,当文本框失去焦点时,文本框中通常会出现一个红色边

我有一个示例,其中我使用一些
TextBox
控件绑定视图模型的属性,包括验证规则。在大多数情况下,这很好。但是,当我尝试包含绑定的
文本框
IsFocused
属性时,在控件中输入无效数字的情况下遇到了问题

当我在直接绑定到视图模型属性的
文本框
控件中输入错误的数字时,错误将按预期显示(文本框周围的红色边框)。但是在与包含查看模型属性和
文本框
多重绑定
绑定的
文本框
中,不会显示错误,并且该值会重置为以前的有效值

例如,如果小于10的数字无效,并且我输入了3,当
文本框
失去焦点时,
文本框
中通常会出现一个红色边框,表示错误。但是在
TextBox
中,包括
IsFocused
作为绑定源,该值会变回以前的有效值(如果在我输入3之前有39,则
TextBox
会变回39)

使用下面的代码可以重现问题:

TestViewModel.cs

public class TestViewModel
{
    public double? NullableValue { get; set; }
}
public class TestMultiBindingConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] != null)
            return values[0].ToString();
        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            double doubleValue;
            var stringValue = value.ToString();
            if (Double.TryParse(stringValue, out doubleValue))
            {
                object[] values = { doubleValue };
                return values;
            }
        }

        object[] values2 = { DependencyProperty.UnsetValue };
        return values2;
    }
}
public class ValidateIsBiggerThanTen : ValidationRule
{
    private const string errorMessage = "The number must be bigger than 10";

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var error = new ValidationResult(false, errorMessage);

        if (value == null)
            return new ValidationResult(true, null);

        var stringValue = value.ToString();
        double doubleValue;
        if (!Double.TryParse(stringValue, out doubleValue))
            return new ValidationResult(true, null);

        if (doubleValue <= 10)
            return error;
        return new ValidationResult(true, null);
    }
}
main window.xaml

<Window x:Class="TestSO34204136TextBoxValidate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSO34204136TextBoxValidate"
        Title="MainWindow" Height="350" Width="525">

  <Window.DataContext>
    <l:TestViewModel/>
  </Window.DataContext>

  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <TextBlock Text="Nullable: "/>
    <TextBox VerticalAlignment="Top" Grid.Column="1">
      <TextBox.Text>
        <MultiBinding Mode="TwoWay">
          <Binding Path="NullableValue"/>
          <Binding Path="IsFocused"
                   RelativeSource="{RelativeSource Self}"
                   Mode="OneWay"/>
          <MultiBinding.ValidationRules>
            <l:ValidateIsBiggerThanTen/>
          </MultiBinding.ValidationRules>
          <MultiBinding.Converter>
            <l:TestMultiBindingConverter/>
          </MultiBinding.Converter>
        </MultiBinding>
      </TextBox.Text>
    </TextBox>
    <TextBox VerticalAlignment="Top" Grid.Column="2"/>
  </Grid>
</Window>
验证biggerthanten.cs

public class TestViewModel
{
    public double? NullableValue { get; set; }
}
public class TestMultiBindingConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] != null)
            return values[0].ToString();
        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            double doubleValue;
            var stringValue = value.ToString();
            if (Double.TryParse(stringValue, out doubleValue))
            {
                object[] values = { doubleValue };
                return values;
            }
        }

        object[] values2 = { DependencyProperty.UnsetValue };
        return values2;
    }
}
public class ValidateIsBiggerThanTen : ValidationRule
{
    private const string errorMessage = "The number must be bigger than 10";

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var error = new ValidationResult(false, errorMessage);

        if (value == null)
            return new ValidationResult(true, null);

        var stringValue = value.ToString();
        double doubleValue;
        if (!Double.TryParse(stringValue, out doubleValue))
            return new ValidationResult(true, null);

        if (doubleValue <= 10)
            return error;
        return new ValidationResult(true, null);
    }
}
public类ValidateIsBiggerThanTen:ValidationRule
{
private const string erromessage=“数字必须大于10”;
公共覆盖验证结果验证(对象值,CultureInfo CultureInfo)
{
var error=新的验证结果(错误,错误消息);
如果(值==null)
返回新的ValidationResult(true,null);
var stringValue=value.ToString();
双重价值;
如果(!Double.TryParse(stringValue,out doubleValue))
返回新的ValidationResult(true,null);

如果(doubleValue您看到的行为的原因是您在
多重绑定中绑定了
文本框
IsFocused
属性。这直接导致在焦点更改时强制更新绑定的目标

在验证失败的场景中,有一个非常短暂的时刻,验证规则已启动,错误已设置,但焦点尚未实际更改。但这一切发生得太快,用户无法看到。由于验证失败,绑定的源未更新

因此,当
IsFocused
属性值更改时,在验证和拒绝输入的值之后,接下来要发生的事情是重新评估绑定(因为其中一个源属性已更改!)以更新目标。由于实际源值从未更改,目标(
文本框
)从键入的内容恢复到源中存储的内容


您应该如何解决此问题?这取决于所需的确切行为。您有三个基本选项:

  • 保持绑定到
    IsFocused
    ,并添加
    UpdateSourceTrigger=“PropertyChanged”
    。这将保持在焦点丢失时复制旧值的基本当前行为,但至少会在编辑值时立即向用户提供验证反馈
  • 完全删除对
    IsFocused
    的绑定。这样绑定的目标将不依赖于此,并且不会在焦点更改时重新计算。问题已解决。:)
  • 保持绑定到
    IsFocused
    ,并添加逻辑,以便与验证的交互不会导致将过时的值复制回
    文本框
根据我们前后的评论,上面的第三个选项似乎是您场景中的首选选项,因为您希望在控件有焦点时与没有焦点时以不同的格式设置值的文本表示形式


我怀疑用户界面是否明智,因为它会根据控件是否聚焦而对数据进行不同的格式设置。当然,焦点改变影响整体视觉表现是完全合理的,但这通常会涉及下划线、突出显示等内容。显示完全不同的字符串依赖项关于控件是否集中的争论似乎可能会干扰用户的理解,并可能使他们感到烦恼

但我同意这是一个主观的观点,很明显,在你的例子中,你有一个特定的行为,这是你的规范所需要的,需要得到支持。因此,考虑到这一点,让我们来看看你如何实现这个行为


如果您希望能够绑定到
IsFocused
属性,但如果源尚未实际更新(即验证错误阻止了这种情况的发生),则不需要更改控件当前内容的焦点副本,然后还可以绑定到
Validation.HasError
属性,并使用该属性控制转换器的行为。例如:

class TestMultiBindingConverter : IMultiValueConverter
{
    private bool _hadError;

    public object Convert(object[] values,
        Type targetType, object parameter, CultureInfo culture)
    {
        bool? isFocused = values[1] as bool?,
            hasError = values[2] as bool?;

        if ((hasError == true) || _hadError)
        {
            _hadError = true;
            return Binding.DoNothing;
        }

        if (values[0] != null)
        {
             return values[0].ToString() + (isFocused == true ? "" : " (+)");
        }

        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value,
        Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            double doubleValue;
            var stringValue = value.ToString();
            if (Double.TryParse(stringValue, out doubleValue))
            {
                object[] values = { doubleValue };
                _hadError = false;
                return values;
            }

        }
        object[] values2 = { DependencyProperty.UnsetValue };
        return values2;
    }
}
上面添加了一个字段
\u hadError
,该字段“记住”控件最近发生的事情。如果在验证检测到错误时调用转换器,则转换器返回
绑定。不执行任何操作(其效果如其名称所示:)),并设置标志。此后,无论发生什么情况,只要设置了该标志,转换器将始终不执行任何操作

清除标志的唯一方法是用户最终输入有效的文本。然后将调用转换器的
ConvertBack()
方法来更新源代码,这样可以清除
\u hadrerror
标志。这确保控件内容不会