2013-02-20 2 views
2

Я просмотрел некоторые сообщения, касающиеся правил валидации, но не нашел проблему, с которой я столкнулся.Правило проверки не обновляется корректно с 2 правилами проверки

Я использую правило проверки для текстового поля в окне WPF. У меня есть две проверки: одна для пустого текстового поля и одна для недопустимых символов с использованием соответствия RegEx.

Моя проблема такова:

В моей Textbox:

  1. Тип A - Works, отображение Nothings
  2. Hit Backspace для пустой строки - Works, выводит сообщение об ошибке проверки «Пожалуйста, введите значение во всех полях "
  3. Тип! - Не работает. Он должен отображать «Недопустимые символы были найдены», но все же отображает «Пожалуйста, введите значение во всех полях».
  4. Backspace to empty string - технически работает, потому что он все еще отображает первую ошибку «Пожалуйста, введите значение во всех полях».
  5. Тип A - Работы, сообщение об ошибке
  6. Тип! - Хорошо, отображается сообщение "Недопустимые символы были найдены

То же самое происходит наоборот

Открытое окно

  1. Тип -..! Хорошо, отображает" Недопустимые символы были найдены. .
  2. Backspace пустая строка - по-прежнему отображается «Недопустимые символы были найдены» вместо «Пожалуйста, введите значение во всех областях

Мой код выглядит следующим образом:

ProviderNew.xaml:

<Label Name="lblProviderName" Content="Provider Name: " 
        Grid.Row="1" Grid.Column="0"/> 

<TextBox Name="txtBoxProviderName" Margin="2" 
    MaxLength="20" CharacterCasing="Upper" 
    Grid.Row="1" Grid.Column="1" LostFocus="TxtBoxProviderNameLostFocus" TextChanged="TxtBoxProviderNameTextChanged"> 
       <TextBox.Text> 
        <Binding ElementName="This" Path="ProviderName" UpdateSourceTrigger="PropertyChanged"> 
         <Binding.ValidationRules> 
          <Domain:ProviderValidation /> 
         </Binding.ValidationRules> 
        </Binding> 
       </TextBox.Text> 
      </TextBox> 

      <TextBlock x:Name="tbNameValidation" Foreground="Red" FontWeight="Bold" Margin="10,0,0,0" 
         Text="{Binding ElementName=txtBoxProviderName, Path=(Validation.Errors), Converter={StaticResource eToMConverter}}" 
         Grid.Row="1" Grid.Column="2" /> 

Privder код позади - ProviderNew.xaml.cs

public static readonly DependencyProperty NewProviderNameProperty = DependencyProperty.Register("ProviderName", 
     typeof (string), typeof(ProviderNew), new UIPropertyMetadata("")); 


    public string ProviderName 
    { 
     get { return (string) GetValue(NewProviderNameProperty); } 
     set { SetValue(NewProviderNameProperty, value); } 
    } 

Value Converter Класс

public class ErrorsToMessageConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     var sb = new StringBuilder(); 
     var errors = value as ReadOnlyCollection<ValidationError>; 

     if (errors != null && errors.Count > 0) 
     { 

      foreach (var e in errors.Where(e => e.ErrorContent != null)) 
      { 
       sb.AppendLine(e.ErrorContent.ToString()); 
      } 
     } 
     return sb.ToString(); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Средний класс Класс

private string _providerName; 
    public string ProviderName 
    { 
     get { return _providerName; } 
     set 
     { 
      _providerName = value; 
      RaisePropertyChanged("ProviderName"); 
     } 
    } 

private void RaisePropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 

     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

Validation Rule

public class ProviderValidation : ValidationRule 
{ 
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
    { 
     var str = value as string; 

     if (str != null && !Regex.IsMatch(str, @"^[a-zA-Z0-9]*$")) 
     { 
      return new ValidationResult(false, "Invalid characters were found."); 
     } 


     if (String.IsNullOrEmpty(str)) 
     { 
      return new ValidationResult(false, "Please enter a value in all fields."); 
     } 

     return new ValidationResult(true, null); 
    } 
} 

То, что я пытался устанавливает как LostFocus и TextChanged события, чтобы заставить обновление с помощью:

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty); 
if (expression != null) 
    expression.UpdateSource(); 

Установка точек останова в методе Validate показывает, что правильные совпадения выполняются, и возвращается правильный ValidationResult, но он не обновляет текст правильно.

Я делаю что-то невероятно глупое?

Любые предложения будут оценены.

Edit 1.

Да, у меня есть, что работает, используя MultiDataTrigger и связывание с текстовых полей.

Что не работает, когда я впервые показываю окно, кнопка включена, что я не хочу, потому что это может позволить пользователю нажать «Сохранить» с пустыми текстовыми полями.

Правило проверки не работает с самого начала, когда открывается окно.

Я устанавливаю фокус на текстовое поле, и если он теряет фокус или вводят неправильные данные, тогда правило проверки вводит и отключает кнопку.

Установка кнопки, которая будет отключена по умолчанию, отключает ее при открытии, но тогда она не включается, если нет ошибок проверки.

я могу заставить его работать, заставляя проверку валидации Правилом на произнесите событие Load, используя

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty); 
if (expression != null) 
    expression.UpdateSource(); 

Но тогда, когда окно первого открывает, он сразу появится сообщение Validation Error «Пожалуйста, введите значение во всех полях ", что мне не очень нравится.

В любом случае, или я не могу иметь лучшее из обоих миров.

Вот код кнопки

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0" 
      Grid.Row="2" Click="BtnSaveClick" > 
     <Button.Style> 
      <Style TargetType="{x:Type Button}"> 
       <Setter Property="IsEnabled" Value="False" /> 
       <Style.Triggers> 
        <MultiDataTrigger> 
         <MultiDataTrigger.Conditions> 
          <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" /> 
          <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" /> 
          <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" /> 
         </MultiDataTrigger.Conditions> 
         <Setter Property="IsEnabled" Value="True" /> 
        </MultiDataTrigger> 
       </Style.Triggers> 
      </Style> 
     </Button.Style>  
    </Button> 

Спасибо,

Neill

Edit 2

Быстрый вопрос. Я не смог найти пространство имен RelayCommand. Поиск другого кода вокруг я нашел пример MVVM от Microsoft, который реализует класс RelayCommand: ICommand.

Это правильно?

Код:

public class RelayCommand : ICommand 
{ 
    #region Fields 

    readonly Action<object> _execute; 
    readonly Predicate<object> _canExecute; 

    #endregion // Fields 

    #region Constructors 

    /// <summary> 
    /// Creates a new command that can always execute. 
    /// </summary> 
    /// <param name="execute">The execution logic.</param> 
    public RelayCommand(Action<object> execute) 
     : this(execute, null) 
    { 
    } 

    /// <summary> 
    /// Creates a new command. 
    /// </summary> 
    /// <param name="execute">The execution logic.</param> 
    /// <param name="canExecute">The execution status logic.</param> 
    public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
      throw new ArgumentNullException("execute"); 

     _execute = execute; 
     _canExecute = canExecute; 
    } 

    #endregion // Constructors 

    #region ICommand Members 

    [DebuggerStepThrough] 
    public bool CanExecute(object parameter) 
    { 
     return _canExecute == null ? true : _canExecute(parameter); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 

    public void Execute(object parameter) 
    { 
     _execute(parameter); 
    } 

    #endregion // ICommand Members 
} 

я реализовал следующий в моем ProviderNew.xaml.cs:

private ICommand saveCommand; 
    public ICommand SaveCommand 
    { 
     get 
     { 
      if (saveCommand == null) 
      { 
       saveCommand = new RelayCommand(p => DoSave(), p => CanSave()); 
      } 
      return saveCommand; 
     } 
    } 

    private bool CanSave() 
    { 
     if (!string.IsNullOrEmpty(ProviderName)) 
     { 
      ??? What goes here? 
     } 


     return true; 
    } 
private bool DoSave() 
    { 
     // Save the information to DB 
    } 

Чтобы быть честным, я не уверен, что должно быть закодированы в блоке в 'if (! string.IsNullOrEmpty (ProviderName))'

Также вы сказали добавить код ICommand в DataContext, поэтому не уверены, что он в нужном месте, потому что, когда я открываю окно, Save is enab le и щелчок он ничего не делает, даже если все поля имеют в нем правильные данные.

Вот код XAML ProviderNew для кнопки

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0" 
      Grid.Row="2" Command="{Binding SaveCommand}" > 
     <Button.Style> 
      <Style TargetType="{x:Type Button}"> 
       <Setter Property="IsEnabled" Value="False" /> 
       <Style.Triggers> 
        <MultiDataTrigger> 
         <MultiDataTrigger.Conditions> 
          <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" /> 
          <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" /> 
          <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" /> 
         </MultiDataTrigger.Conditions> 
         <Setter Property="IsEnabled" Value="True" /> 
        </MultiDataTrigger> 
       </Style.Triggers> 
      </Style> 
     </Button.Style>  
    </Button> 

Ваша помощь очень высоко ценится.

С уважением,

Neill

+0

Такая же проблема существует с IDataErrorInfo проверки вместо ValidationRule, в случае, если кто-то имеет эту проблему –

ответ

2

Хорошо мне удалось воспроизвести эту проблему, и это не давало мне покоя, что она не работает. Но я понял это.
Проблема заключается в том, что преобразователь не запускается, когда ошибка изменяется, поскольку фактическое свойство (ProviderName) не изменяется из-за ValidationRule. Вы можете увидеть это поведение, если поместите контрольную точку в конвертер, ValidationRule и Property-setter.

Таким образом, вместо того, чтобы использовать IValueConverter, вы должны изменить связывание tbNameValidation к следующему:

<TextBlock x:Name="tbNameValidation" 
      Text="{Binding ElementName=txtBoxProviderName, 
        Path=(Validation.Errors).CurrentItem.ErrorContent}"> 

важной частью является Path=(Validation.Errors).CurrentItem.ErrorContent". Это обеспечит отображение текущего сообщения об ошибке.

Кроме того, если вы хотели бы «Пожалуйста, введите значение во всех областях» сообщение, чтобы показать с самого начала, вы должны добавить Mode=TwoWay к связыванию txtBoxProviderName:

<Binding ElementName="This" 
      Path="ProviderName" 
      UpdateSourceTrigger="PropertyChanged" 
      Mode="TwoWay"> 

EDIT для part2

зафиксировать включено свойство кнопки, вы можете использовать один и тот же код, как у вас есть в вашем вопросе, но вместо того, чтобы использовать Click события для запуска сохранения-кода, вы можете использовать Command свойство вместо:

в вашем DataContext, создать свойство типа ICommand и связать кнопку с этим:

<Button Command="{Binding SaveCommand}" .... /> 

private ICommand saveCommand; 
public ICommand SaveCommand { 
    get { 
     if (saveCommand == null) { 
      saveCommand = new RelayCommand(p => DoSave(), p=> CanSave()); 
     } 
     return saveCommand; 
     } 
} 

и реализация CanSave может проверить, если все ожидания оправдываются:

private bool CanSave(){ 
    if (!string.IsNullOrEmpty(ProviderName)){ 
     //.... etc 
    } 
} 

CanSave() определит состояние вашей кнопки, потому что CommandBinding автоматически позаботится о включении кнопки в соответствии с методом ограниченной команды CanExecute. Логика вашего валидатора вернется сюда, хотя, может быть, неплохо найти способ повторного использования этого кода.

больше информации о Relaying Commands

+0

Спасибо так много. Теперь он отлично работает. Вы уже ответили, что будет моим вторым вопросом. Чтобы выполнить проверку правильности с самого начала, потому что у меня есть кнопка «Сохранить», которая не включена, если проверка правильности не выполняется. Режим = «TwoWay», однако, этого не делает. Не так ли в любом случае режим двустороннего доступа по умолчанию? – Neill

+0

вы можете использовать свойство 'Validation.HasError' для управления состоянием кнопки Save, либо через' Binding', либо через 'Trigger' – RoelF

+0

. См. Редактировать 1 в сообщении, слишком короткое для всей информации здесь. Спасибо – Neill