为什么TextBox的值重置为先前的值而不是显示错误?

时间:2015-12-10 14:15:32

标签: c# wpf validation multibinding

我有一个示例,我将视图模型的属性绑定到一些TextBox控件,包括验证规则。在大多数情况下,这很好。但是当我尝试包含绑定IsFocused的{​​{1}}属性时,如果在控件中输入了无效的数字,我会遇到麻烦。

当我在直接绑定到视图模型属性的TextBox控件中输入错误的数字时,错误会按预期显示(TextBox周围的红色边框)。但是在绑定了TextBox的{​​{1}}中,包含TextBox的视图模型属性和MultiBinding属性,错误未显示且值得到重置为上一个有效值。

例如,如果小于10的数字无效,并且我输入3,则当IsFocused失去焦点时,通常会在TextBox中出现红色边框,表示错误。但是在包含TextBox TextBox作为其绑定来源的TextBox中,值会更改回先前的有效值(如果在我输入3之前有39,则IsFocused会更改回来到39)。

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

TestViewModel.cs

TextBox

MainWindow.xaml

public class TestViewModel
{
    public double? NullableValue { get; set; }
}

TestMultiBindingConverter.cs

<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>

<强烈> ValidateIsBiggerThanTen.cs

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); } } 没有显示错误?

1 个答案:

答案 0 :(得分:3)

您所看到的行为的原因特别是您已绑定TextBoxIsFocused的{​​{1}}媒体资源。当焦点发生变化时,这会直接强制更新绑定的目标

在验证失败的情况下,验证规则触发的时间非常短暂,错误已设置,但焦点尚未实际更改。但这一切都发生得太快,用户无法看到。由于验证失败,绑定源不会更新。

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


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

  • 继续绑定TextBox,然后添加IsFocused。这将保留在焦点丢失时复制旧值的基本当前行为,但至少会在编辑值时为用户提供即时验证反馈。
  • 完全删除与UpdateSourceTrigger="PropertyChanged"的绑定。然后,绑定的目标不依赖于此,并且在焦点改变时不会被重新评估。问题解决了。 :)
  • 继续绑定IsFocused,并添加逻辑,以便与验证的交互不会导致将陈旧值复制回IsFocused

根据我们来回的评论,似乎上面的第三个选项是您的场景的首选选项,因为您希望在控件具有焦点时不同地格式化值的文本表示。


我怀疑格式化数据的用户界面的智慧取决于控件是否集中。当然,完全有意义的焦点更改会影响整体视觉呈现,但这通常会涉及下划线,突出显示等等。根据控件是否聚焦显示完全不同的字符串似乎可能会干扰用户理解和也可能会惹恼他们。

但我同意这是一个主观点,显然在你的情况下,你有这个特定的行为,这对你的规范是理想的,需要得到支持。因此,考虑到这一点,让我们看看如何实现这种行为......


如果您希望能够绑定到TextBox属性,但是如果源尚未实际更新(例如,如果验证错误阻止了这种情况发生),则无法将更改集中在控件的当前内容上),然后您还可以绑定到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; } } ,以及#34;记住&#34;最近对控件发生了什么。如果在验证检测到错误时调用转换器,转换器将返回_hadError(具有其名称暗示的效果:)),并设置标志。此后,无论发生什么,只要设置了该标志,转换器将始终不执行任何操作。

标记将被清除的唯一方法是用户最终输入有效的文本。然后将调用转换器的Binding.DoNothing方法来更新源,并且这样做可以清除ConvertBack()标志。这可确保控件内容永远不会因绑定更新而被覆盖,除非自上次更新源以来没有错误。

以上更新了XAML示例以使用其他绑定输入:

_hadError

我应该指出,如果不明显:<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="Nulleable: "/> <TextBox x:Name="textBoxWrapper" Grid.Column="1" VerticalAlignment="Top"> <TextBox.Text> <MultiBinding x:Name="TextBoxBinding" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> <Binding Path="NulleableValue"/> <Binding Path="IsFocused" RelativeSource="{RelativeSource Self}" Mode="OneWay"/> <Binding Path="(Validation.HasError)" 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> 字段用于转换器本身。为了使上述功能正常工作,您需要为其应用的每个绑定提供一个单独的转换器实例。有其他方法可以唯一地跟踪每个控件的这种标志,但我觉得在这方面对选项的扩展讨论超出了这个问题的范围。如果您无法自行解决问题,请随意自行探索并发布有关该方面的新问题。

相关问题