Validation Binding on First Load

自闭症网瘾萝莉.ら 提交于 2019-12-22 03:56:30

问题


I'm still struggling with validation in WPF.

I've got a custom validation rule which requires text to appear in a textbox i.e. it enforces a mandatory field constraint.

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

The problem is, that when the Window first loads, there is no text in the TextBox (as you would expect). But the Text property is being bound to a property on the ViewModel, and as such, the validation Rule is firing, indicating that there is a problem with the Window - before the user has even had an opportunity to violate a business rule.

Is this a problem which has been solved before? I can't have been the 1st to experience this. I'm sure it is a trap for young players.


回答1:


There are a couple of patterns for this. I usually implement on the class/Model the ISupportInitialize interface, which will require you creating BeginInit() and EndInit() in those methods I simply set a private bool _isInitializing to true or false.

In the view model or where/when you create/populate your model/class wrap it with begin and end init:

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

So then depending how your ValidationRule is invoked, you could check the state of your _isInitializing to see if you need to validate.

Lately I've been using attribute validators which fire on PropertyChanged so you could do something like:

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

If you don't want to bother with ISupportInitialize, then pass all the values that you need in your properties during construction not the property. Binding will query the getters on you properties first time and will get their values, and after anything will go through property setter and get validated:

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



回答2:


It's been a while and I should have updated this question. I resolved it using a class which I found in a WPF book by Ian Griffths (an O'Reilly book):

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

}

Then, on the view, I had the following configuration:

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

Worked like a charm! I just called the IsValid method every time I wanted to manually validate e.g. on a button press.



来源:https://stackoverflow.com/questions/9802290/validation-binding-on-first-load

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!