【问题标题】:WPF ValidationRule prevent last value to be setWPF ValidationRule 阻止设置最后一个值
【发布时间】:2019-03-02 00:50:27
【问题描述】:

我在 TextBox 上使用验证规则来验证用户输入字符串。 Text 绑定到视图模型上的浮点属性,并且 WPF 绑定引擎非常热衷于为我自动将字符串转换为浮点数。

但是,当验证失败时,绑定似乎会读回旧值。这导致文本框周围出现红色边框,即使文本已恢复为最后一个可接受的浮点值。

问题:如何确保验证失败时绑定引擎不会自动覆盖错误的输入文本? 绑定必须是双向的。

我应该提到我在我的 ValidationRule 中做了一个小技巧,我让它从视图模型定位器中找到当前视图模型,并在视图模型上使用 INotifyDataErrorInfo 方法。我发现这是一个很好的解决方案,因为这意味着 ViewModel HasError 将为我收集所有验证错误(它让我在设置属性时在验证规则或视图模型中应用验证)。让验证的好处规则在视图模型上使用 INotifyDataErrorInfo 应用验证是可以在从字符串自动转换为浮点数之前应用验证,确保即使用户键入“Hello World”导致异常也执行验证(被绑定引擎)在自动转换为浮动期间。这让我可以在虚拟机上保持浮动的属性类型,并且仍然可以执行验证。

XAML

<TextBox Grid.Row="2" Grid.Column="2" x:Name="txtPreHeight" 
                         HorizontalAlignment="Stretch" 
                         HorizontalContentAlignment="Stretch"
                         VerticalAlignment="Center" 
                         Template="{DynamicResource TextBoxBaseControlTemplateMainScreen}">
  <TextBox.Text>
                    <Binding 
                     Path="PreHeight"    
                        ValidatesOnExceptions="False"
                     NotifyOnValidationError="True"
                     ValidatesOnNotifyDataErrors="True"  
                     UpdateSourceTrigger="LostFocus" 
                     >
                        <Binding.ValidationRules>
                            <validationrules:PreHeightValidationRule ViewModelType="GotoPositionViewModel" Min="0" Max="100" ValidationStep="RawProposedValue"/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
                <i:Interaction.Triggers>
                    <helper:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}">
                        <cmd:EventToCommand Command="{Binding SetFocusOnValidationErrorCommand}"
                        PassEventArgsToCommand="True" />
                    </helper:RoutedEventTrigger>
                </i:Interaction.Triggers>
            </TextBox>

验证规则

class PreHeightValidationRule : ValidationRule
{
    private ValidationService validationService_;

    private Int32 min_ = Int32.MaxValue;
    private Int32 max_ = Int32.MinValue;
    private string viewModelType_ = null;

    public PreHeightValidationRule()
    {
        validationService_ = ServiceLocator.Current.GetInstance<Validation.ValidationService>();
    }

    public Int32 Min
    {
        get { return min_; }
        set { min_ = value; }
    }

    public Int32 Max
    {
        get { return max_; }
        set { max_ = value; }
    }

    public string ViewModelType
    {
        get { return viewModelType_; }
        set { viewModelType_ = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
    {
        ValidationResult result = base.Validate(value, cultureInfo, owner);
        ViewModel.ViewModelBaseWithNavigation vm;
        System.Reflection.Assembly asm = typeof(ViewModelLocator).Assembly;
        Type type = null;

        if (type == null)
            type = asm.GetType(ViewModelType);

        if (type == null)
            type = asm.GetType("TeachpendantControl.ViewModel." + ViewModelType);

        vm = (ViewModel.ViewModelBaseWithNavigation)ServiceLocator.Current.GetInstance(type);
        ICollection<string> validationErrors = new List<string>();

        try
        {
            validationService_.ValidatePreHeight(value.ToString(), ref validationErrors, Min, Max);
        }
        catch (Exception e)
        {
            validationErrors.Add("Failed to validate, Exception thrown " + e.Message);
        }
        finally
        {
            vm.UpdateValidationForProperty(((BindingExpression)owner).ResolvedSourcePropertyName, validationErrors, validationErrors.Count == 0);
        }

        return new ValidationResult(validationErrors.Count == 0, validationErrors);
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        return new ValidationResult(false, null);
    }
}

【问题讨论】:

  • @mm8 请在下面查看我的回答。我设法解决了它。非常值得看看这种方法。与以“正常”方式使用 ValidationRule 或仅在 VM 属性设置器中使用验证相比,它具有相当多的优点,并且它允许 VM 属性成为请求的类型(无需使用额外的字符串层来强制对可能导致绑定异常的输入进行验证)

标签: c# .net wpf mvvm-light validationrules


【解决方案1】:

我设法解决了! 我从 Josh 那里找到了一个提示,让我的注意力转向了正确的方向。

使用转换器可以设置 Binding.DoNothing。我将其修改为检查 VM 上的 HasError 的转换器。在 HasError 的情况下我返回 Binding.DoNothing 否则我只是转发该值。

using CommonServiceLocator;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;   

namespace Converters
{
   class HasErrorToBindingDoNothingConverter : DependencyObject, IValueConverter
   {
      public static readonly DependencyProperty ViewModelTypeProperty =
        DependencyProperty.Register("ViewModelType", typeof(string), typeof(HasErrorToBindingDoNothingConverter), new UIPropertyMetadata(""));

    public string ViewModelType
    {
        get { return (string)GetValue(ViewModelTypeProperty); }
        set { SetValue(ViewModelTypeProperty, value); }
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            ViewModel.ViewModelBaseWithNavigation vm;
            System.Reflection.Assembly asm = typeof(ViewModelLocator).Assembly;
            Type type = null;

            if (type == null)
                type = asm.GetType(ViewModelType);

            if (type == null)
                type = asm.GetType("TeachpendantControl.ViewModel." + ViewModelType);

            vm = (ViewModel.ViewModelBaseWithNavigation)ServiceLocator.Current.GetInstance(type);

            if (vm.HasErrors)
                return Binding.DoNothing;
            else
                return value;
        }
        catch { return value; }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}
}

我不得不将 XAML 更改为此

  <TextBox.Text>
                    <Binding 
                     Path="PreHeight"    
                     ValidatesOnExceptions="False"
                     NotifyOnValidationError="False"
                     ValidatesOnNotifyDataErrors="False"     
                     UpdateSourceTrigger="PropertyChanged" 
                     Mode="TwoWay"
                     >
                        <Binding.ValidationRules>
                            <validationrules:PreHeightValidationRule ViewModelType="GotoPositionViewModel" Min="0" Max="100" ValidationStep="RawProposedValue"/>
                        </Binding.ValidationRules>
                        <Binding.Converter>
                            <converters:HasErrorToBindingDoNothingConverter ViewModelType="GotoPositionViewModel"/>
                        </Binding.Converter>
                    </Binding>
                </TextBox.Text>

            </TextBox>

IMO 这是一个很好的解决方案,值得牢记。

优点

  • 使用 INotifyDataErrorInfo 在 VM 上进行验证
  • 视图元素可以直接绑定到 INotifyDataErrorInfo HasError。
  • 支持多个 ValdiationResult(失败)/属性。
  • 支持跨属性验证。
  • 可以在 RawProposedValue(字符串)上使用 ValidationRule 进行验证,无需在 VM 中添加额外的字符串层。
  • 当不需要对 RawProposedValue 执行验证时,可以在 ViewModel 中的属性设置器处进行验证。
  • 最后一点意味着可以在自动转换(在这种情况下从字符串到浮点数)失败之前执行验证,并由 WPF 绑定引擎捕获异常,这通常会阻止验证不执行并阻止元素绑定到 HasError不更新他们的状态。
  • 验证失败时视图中不会覆盖不正确的值(本例中为字符串)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多