首次加载时验证绑定

12

我仍然在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>
问题是,当窗口首次加载时,文本框中没有文本(正如您所预期的那样)。但是,文本属性已绑定到 ViewModel 上的一个属性,因此验证规则正在触发,表明窗口存在问题 - 即使用户还没有违反任何业务规则也是如此。
这是以前解决过的问题吗?我肯定不是第一个遇到这个问题的人。我相信这对年轻的程序员来说是一个陷阱。

你能试一下... UpdateSourceTrigger="LostFocus" 吗? - WPF-it
当用户第一次更改某个字段时,您可以创建一个验证组,并仅启用它。 - Vladimir Perevalov
@AngelWPF 我已经尝试过了。当窗口加载时,它仍然会在初始绑定时进行验证。 - onefootswill
@VladimirPerevalov 能够启用和禁用绑定吗?如果可以的话,我应该只需在第一次加载时禁用绑定。但是如何启用它呢?捕获用户第一次更改的逻辑与每个未来更改不同,听起来相当复杂。我不确定为什么一个简单的RequiredField验证器会这么困难。 - onefootswill
我同意@Blam的观点,将验证放在setter中似乎效果最好...现在你可能需要通过代码设置属性,这将强制进行验证,但也有办法规避这个问题。 - denis morozov
显示剩余2条评论
2个回答

0

好久不见,我应该更新一下这个问题。我使用了一个类来解决它,这个类是我在 Ian Griffths(一本 O'Reilly 书籍的作者)的 WPF 书中找到的:

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 - Manuzor
我不再拥有这个代码,但是据我记忆,在按钮按下时,你需要传入父级。类似于 Validator.IsValid(grid) - onefootswill
我明白了,所以你必须在源端触发这个。谢谢澄清。 - Manuzor

0

有几种模式可以实现这个。我通常在类/模型上实现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以获取其值,并且在任何操作通过属性setter并进行验证后才会进行:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接