Проверка привязки при первой загрузке

Я все еще борюсь с проверкой в ​​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>

Проблема заключается в том, что при первом загрузке окна в TextBox нет текста (как и следовало ожидать). Но свойство Text привязано к свойству ViewModel, и как правило, правило проверки запускается, указывая на то, что есть проблема с окном - до того, как пользователь даже имел возможность нарушить бизнес-правило.

Это проблема, которая была решена раньше? Я не мог быть первым, кто испытал это. Я уверен, что это ловушка для молодых игроков.

Ответ 1

Это было какое-то время, и я должен был обновить этот вопрос. Я разрешил его, используя класс, который я нашел в книге 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 каждый раз, когда я хотел вручную проверить, например. при нажатии кнопки.

Ответ 2

Для этого есть несколько шаблонов. Обычно я использую интерфейс класса /Model ISupportInitialize класса class, который потребует создания BeginInit() и EndInit() в этих методах. Я просто устанавливаю private bool _isInitializing в true или false.

В модели представления или где/когда вы создаете/заполняете свою модель/класс, оберните ее с помощью начала и конца 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, тогда передайте все значения, которые вам нужны в ваших свойствах во время строительства, а не в качестве свойства. Привязка будет запрашивать у вас геттеры в ваших свойствах в первый раз и получит их значения, и после того, как все пройдет через свойство setter и получит подтверждение:

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