2017-01-30 11 views
2

Я работаю над WPF-приложением с шаблоном MVVM, в котором я использую DataAnnotations для проверки. Так я реализовал такое решение, как в этом article.Как проверить, нет ли допустимых чисел в WPF с DataAnnotations

Затем я попытался добавить свойство к моему ViewModel - называется «Возраст» - который должен принимать только цифры и имеют диапазон от 1 до 100.

[RegularExpression(@"^[0-9]{1,3}$", ErrorMessage = "Please enter a valid number")] 
    [Range(1, 100, ErrorMessage = "Age should be between 1 to 100")] 
    /// <summary> 
    /// Gets or sets the age. 
    /// </summary> 
    public int Age 
    { 
     get { return GetValue(() => Age); } 
     set { SetValue(() => Age, value); } 
    } 

И в моем окне WPF я получил текстовое поле который связан с Возраст:

<TextBox x:Name="tbx_age" 
      ToolTip="The age" 
      Text="{Binding Age, NotifyOnSourceUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"> 
    </TextBox> 

Как я начинаю приложение, текстовое поле предварительно распределено с нулем («0»). Когда я заменяю ноль в текстовом поле на «1a», я получаю сообщение об ошибке «Значение« 1a »не может быть преобразовано». Это не регулярное сообщение из моего кода, и я не могу объяснить, откуда он. Я сделал ошибку в регулярном выражении или что-то еще?

Я загрузил свой TestProject на GitHub: Repository Проект, который я имею в виду называется «Validation_DataAnnotations».

Заранее благодарен!

Это класс PropertyChangedNotification, который я использую для уведомления и проверки:

public abstract class PropertyChangedNotification : INotifyPropertyChanged, IDataErrorInfo 
{ 
    #region Fields 

    private readonly Dictionary<string, object> _values = new Dictionary<string, object>(); 

    #endregion 

    #region Protected 

    /// <summary> 
    /// Sets the value of a property. 
    /// </summary> 
    /// <typeparam name="T">The type of the property value.</typeparam> 
    /// <param name="propertySelector">Expression tree contains the property definition.</param> 
    /// <param name="value">The property value.</param> 
    protected void SetValue<T>(Expression<Func<T>> propertySelector, T value) 
    { 
     string propertyName = GetPropertyName(propertySelector); 

     SetValue<T>(propertyName, value); 
    } 

    /// <summary> 
    /// Sets the value of a property. 
    /// </summary> 
    /// <typeparam name="T">The type of the property value.</typeparam> 
    /// <param name="propertyName">The name of the property.</param> 
    /// <param name="value">The property value.</param> 
    protected void SetValue<T>(string propertyName, T value) 
    { 
     if (string.IsNullOrEmpty(propertyName)) 
     { 
      throw new ArgumentException("Invalid property name", propertyName); 
     } 

     _values[propertyName] = value; 
     NotifyPropertyChanged(propertyName); 
    } 

    /// <summary> 
    /// Gets the value of a property. 
    /// </summary> 
    /// <typeparam name="T">The type of the property value.</typeparam> 
    /// <param name="propertySelector">Expression tree contains the property definition.</param> 
    /// <returns>The value of the property or default value if not exist.</returns> 
    protected T GetValue<T>(Expression<Func<T>> propertySelector) 
    { 
     string propertyName = GetPropertyName(propertySelector); 

     return GetValue<T>(propertyName); 
    } 

    /// <summary> 
    /// Gets the value of a property. 
    /// </summary> 
    /// <typeparam name="T">The type of the property value.</typeparam> 
    /// <param name="propertyName">The name of the property.</param> 
    /// <returns>The value of the property or default value if not exist.</returns> 
    protected T GetValue<T>(string propertyName) 
    { 
     if (string.IsNullOrEmpty(propertyName)) 
     { 
      throw new ArgumentException("Invalid property name", propertyName); 
     } 

     object value; 
     if (!_values.TryGetValue(propertyName, out value)) 
     { 
      value = default(T); 
      _values.Add(propertyName, value); 
     } 

     return (T)value; 
    } 

    /// <summary> 
    /// Validates current instance properties using Data Annotations. 
    /// </summary> 
    /// <param name="propertyName">This instance property to validate.</param> 
    /// <returns>Relevant error string on validation failure or <see cref="System.String.Empty"/> on validation success.</returns> 
    protected virtual string OnValidate(string propertyName) 
    { 
     if (string.IsNullOrEmpty(propertyName)) 
     { 
      throw new ArgumentException("Invalid property name", propertyName); 
     } 

     string error = string.Empty; 
     var value = GetValue(propertyName); 
     var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>(1); 
     var result = Validator.TryValidateProperty(
      value, 
      new ValidationContext(this, null, null) 
      { 
       MemberName = propertyName 
      }, 
      results); 

     if (!result) 
     { 
      var validationResult = results.First(); 
      error = validationResult.ErrorMessage; 
     } 

     return error; 
    } 

    #endregion 

    #region Change Notification 

    /// <summary> 
    /// Raised when a property on this object has a new value. 
    /// </summary> 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// Raises this object's PropertyChanged event. 
    /// </summary> 
    /// <param name="propertyName">The property that has a new value.</param> 
    protected void NotifyPropertyChanged(string propertyName) 
    { 
     this.VerifyPropertyName(propertyName); 

     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      var e = new PropertyChangedEventArgs(propertyName); 
      handler(this, e); 
     } 
    } 

    protected void NotifyPropertyChanged<T>(Expression<Func<T>> propertySelector) 
    { 
     var propertyChanged = PropertyChanged; 
     if (propertyChanged != null) 
     { 
      string propertyName = GetPropertyName(propertySelector); 
      propertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    #endregion // INotifyPropertyChanged Members 

    #region Data Validation 

    string IDataErrorInfo.Error 
    { 
     get 
     { 
      throw new NotSupportedException("IDataErrorInfo.Error is not supported, use IDataErrorInfo.this[propertyName] instead."); 
     } 
    } 

    string IDataErrorInfo.this[string propertyName] 
    { 
     get 
     { 
      return OnValidate(propertyName); 
     } 
    } 

    #endregion 

    #region Privates 

    private string GetPropertyName(LambdaExpression expression) 
    { 
     var memberExpression = expression.Body as MemberExpression; 
     if (memberExpression == null) 
     { 
      throw new InvalidOperationException(); 
     } 

     return memberExpression.Member.Name; 
    } 

    private object GetValue(string propertyName) 
    { 
     object value; 
     if (!_values.TryGetValue(propertyName, out value)) 
     { 
      var propertyDescriptor = TypeDescriptor.GetProperties(GetType()).Find(propertyName, false); 
      if (propertyDescriptor == null) 
      { 
       throw new ArgumentException("Invalid property name", propertyName); 
      } 

      value = propertyDescriptor.GetValue(this); 
      _values.Add(propertyName, value); 
     } 

     return value; 
    } 

    #endregion 

    #region Debugging 

    /// <summary> 
    /// Warns the developer if this object does not have 
    /// a public property with the specified name. This 
    /// method does not exist in a Release build. 
    /// </summary> 
    [Conditional("DEBUG")] 
    [DebuggerStepThrough] 
    public void VerifyPropertyName(string propertyName) 
    { 
     // Verify that the property name matches a real, 
     // public, instance property on this object. 
     if (TypeDescriptor.GetProperties(this)[propertyName] == null) 
     { 
      string msg = "Invalid property name: " + propertyName; 

      if (this.ThrowOnInvalidPropertyName) 
       throw new Exception(msg); 
      else 
       Debug.Fail(msg); 
     } 
    } 

    /// <summary> 
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used 
    /// when an invalid property name is passed to the VerifyPropertyName method. 
    /// The default value is false, but subclasses used by unit tests might 
    /// override this property's getter to return true. 
    /// </summary> 
    protected virtual bool ThrowOnInvalidPropertyName { get; private set; } 

    #endregion // Debugging Aides 
} 
+0

Эта ошибка возникает как исключение или сообщение в текстовом поле? – Sebi

+0

Это происходит как подсказка элемента textbox. В XAML я определил стиль отображения ошибок проверки. –

ответ

0

Как int собственности не может никогда быть установлен на что-либо другое, чем int значение, чтобы ваша собственность сеттер никогда не будет вызываться, если вы установите Text свойство TextBox к «1а». Никакое регулярное выражение или аннотация данных в мире не решит этого.

Что вы можете сделать, чтобы настроить сообщение об ошибке, которое появляется, когда во время выполнения WPF пытается преобразовать значение «1a» к int перед свойство установлено, чтобы использовать ValidationRule:

<TextBox> 
    <TextBox.Text> 
     <Binding Path="Age" UpdateSourceTrigger="PropertyChanged"> 
      <Binding.ValidationRules> 
       <local:StringToIntValidationRule ValidationStep="RawProposedValue"/> 
      </Binding.ValidationRules> 
     </Binding> 
    </TextBox.Text> 
    ... 
</TextBox> 

public class StringToIntValidationRule : ValidationRule 
{ 
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
    { 
     int i; 
     if (int.TryParse(value.ToString(), out i)) 
      return new ValidationResult(true, null); 

     return new ValidationResult(false, "Please enter a valid integer value."); 
    } 
} 

Существует полный пример и более подробная информация об этом доступна в следующем блоге: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/

Примечание т что модель представления не несет ответственность за проверку того, что ее свойство Age имеет значение int. Это ответственность за контроль или представление.

+0

Hello mm8. Большое спасибо за ваше решение. Как вы можете видеть в моем репозитории GitHub, у меня был тот же код в проекте «Validation_ValidationRule». –

+0

Хорошо, я не проверял :) Пожалуйста, не забудьте проголосовать за ответ. – mm8

1

Вы сшиваться TextBox.TextProperty против ИНТ-свойство «Возраст», таким образом WPF уже нужно преобразовать строку целое число, чтобы присвоить значение вашему свойству.

Если вы хотите сделать все преобразование самостоятельно, вы можете привязать его к строке-Свойству. В качестве альтернативы вы можете просто установить ValidatesOnDataErrors = False в вашей привязке, тогда нечисловые значения будут игнорироваться без изменений, числовые значения изменят свойство bound.