【问题标题】:Validation Binding on First Load首次加载时的验证绑定
【发布时间】:2012-03-21 10:03:49
【问题描述】:

我仍在为 WPF 中的验证而苦苦挣扎。

我有一个自定义验证规则,它要求文本出现在文本框中,即它强制执行强制字段约束。

<TextBox local:Masking.Mask="^[a-zA-Z0-9]*$" x:Name="CameraIdCodeTextBox" Grid.Row="1" Grid.Column="1">
  <Binding Path="CameraIdCode" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" ValidatesOnExceptions="True">
    <Binding.ValidationRules>
      <localValidation:RequiredFieldRule />
    </Binding.ValidationRules>
  </Binding>
</TextBox>

问题是,当 Window 首次加载时,TextBox 中没有文本(如您所料)。但是 Text 属性被绑定到 ViewModel 上的属性,因此,验证规则正在触发,表明 Window 存在问题 - 在用户甚至有机会违反业务规则之前。

这是以前解决过的问题吗?我不可能是第一个体验到这一点的人。我敢肯定这对年轻球员来说是个陷阱。

【问题讨论】:

  • 你能试试吗... UpdateSourceTrigger="LostFocus"
  • 您可以创建一个验证组,并且只有在用户首次更改某个字段时才启用它。
  • @AngelWPF 我试过了。它仍然在窗口加载时验证初始绑定。
  • @VladimirPerevalov 是否可以启用和禁用绑定?如果是这样,我可能应该在第一次加载时禁用绑定。但是我该如何启用它?捕捉用户第一次变化的逻辑,与未来的每一次变化不同,这非常复杂。我不确定为什么简单的RequiredField 验证器如此困难。
  • 您可能需要将验证移至集合;因为 set 在初始绑定时不会被调用。或者可能在验证中允许 string.empty 但在集合中拒绝 string.empty; (并引发验证错误)。

标签: wpf validation binding


【解决方案1】:

对此有几种模式。我通常在类/模型上实现ISupportInitialize 接口,这将要求您在这些方法中创建BeginInit()EndInit(),我只需将私有布尔_isInitializing 设置为true 或false。

在视图模型中或创建/填充模型/类的位置/时间用 begin 和 end init 包装它:

var o = new SampleObject();
o.BeginInit()
o.StartDate = DateTime.Now; //just some sample property...
o.EndInit();

然后根据您的 ValidationRule 的调用方式,您可以检查 _isInitializing 的状态以查看是否需要验证。

最近我一直在使用在PropertyChanged 上触发的属性验证器,因此您可以执行以下操作:

[CustomValidator("ValidateStartDate")]
 public DateTime StartDate
 { get ...
 {
   set
     {
       if(_startDate == value) return;
       _startDate = value;
       if(_isInitializing) return;
       RaisePropertyChange(() => StartDate);
      }..

如果您不想打扰ISupportInitialize,请在构造期间传递您在属性中需要的所有值,而不是属性。绑定将第一次查询您的属性的 getter 并获取它们的值,之后将通过属性设置器并得到验证:

 //c-tor
 public MyObject(DateTime start)
 {
    _startDate = start;
 }

【讨论】:

    【解决方案2】:

    已经有一段时间了,我应该更新这个问题。我使用 Ian Griffths 的 WPF 书籍(O'Reilly 的书籍)中找到的一个类解决了这个问题:

    public static class Validator
    {
        /// <summary>
        /// This method forces WPF to validate the child controls of the control passed in as a parameter.
        /// </summary>
        /// <param name="parent">Type: DependencyObject. The control which is the descendent root control to validate.</param>
        /// <returns>Type: bool. The validation result</returns>
        public static bool IsValid(DependencyObject parent)
        {
            // Validate all the bindings on the parent
            bool valid = true;
            LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
            while (localValues.MoveNext())
            {
                LocalValueEntry entry = localValues.Current;
                if (BindingOperations.IsDataBound(parent, entry.Property))
                {
                    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                    foreach (ValidationRule rule in binding.ValidationRules)
                    {
                        ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
                        if (!result.IsValid)
                        {
                            BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                            Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
                            valid = false;
                        }
                    }
                }
            }
    
            // Validate all the bindings on the children
            for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                if (!IsValid(child))
                {
                    valid = false;
                }
            }
    
            return valid;
        }
    
    }
    

    然后,在视图上,我有以下配置:

    <TextBox local:Masking.Mask="^[0-9]*$" IsEnabled="{Binding Path=LocationNumberEnabled}" Grid.Row="1" Grid.Column="3">
        <Binding Path="LocationNumber"  Mode="TwoWay" UpdateSourceTrigger="LostFocus" NotifyOnValidationError="True" ValidatesOnExceptions="True">
            <Binding.ValidationRules>
                <localValidation:PositiveNumberRule />
                <localValidation:RequiredFieldRule />
            </Binding.ValidationRules>
        </Binding>                    
    </TextBox>
    

    工作就像一个魅力!每次我想手动验证时我都调用了 IsValid 方法,例如按下按钮。

    【讨论】:

    • C# 和 XAML 代码似乎没有相互交互。你应该如何以及在哪里使用Validator.IsValid
    • 我不再有这个的代码,但是作为记忆服务,在按钮按下时,你传递了父级。类似于Validator.IsValid(grid)
    • 我明白了,所以你必须在源端触发它。感谢您的澄清。
    猜你喜欢
    • 1970-01-01
    • 2018-05-18
    • 1970-01-01
    • 2017-07-07
    • 2017-01-27
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    • 2022-07-04
    相关资源
    最近更新 更多