c# - 如何使用 DataAnnotations 检查 WPF 中是否存在有效数字

标签 c# wpf mvvm data-annotations

我正在开发一个具有 MVVM 模式的 WPF 应用程序,其中我使用 DataAnnotations 进行验证。 所以我在article中实现了类似的解决方案.

然后我尝试向我的 View 模型添加一个名为“Age”的属性,该属性仅接受数字且范围在 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 窗口上,我有一个与年龄绑定(bind)的文本框:

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

当我启动应用程序时,文本框已预先分配为零(“0”)。 当我将文本框中的零替换为“1a”时,我收到错误消息“无法转换值“1a””。 这不是我的代码中的常规消息,我无法解释它来自哪里。 我是否在正则表达式或其他方面犯了错误?

我已将我的测试项目上传到 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
}

最佳答案

由于 int 属性永远不能设置为 int 值以外的任何值,因此如果您设置,您的属性 setter 将永远不会被调用将 TextBox 的 Text 属性设置为“1a”。世界上没有正则表达式或数据注释可以解决这个问题。

在设置属性之前,当 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/

请注意, View 模型没有责任验证其 Age 属性是否设置为 int 值。这是控件或 View 的责任。

关于c# - 如何使用 DataAnnotations 检查 WPF 中是否存在有效数字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41936211/

相关文章:

wpf - MVVM 模式违规 : MediaElement. Play()

c# - 自定义搜索组合框

wpf - MVVM WPF 中 ListView 复选框的选定项

c# - 如何使用 System.ServiceModel.Syndicate 从 RSS 源读取 "dc:creator"元素

c# - Settings.Designer 文件和静态

WPF:再次尝试向 Window.Resources 添加类

wpf - 如何重用 wpf/mvvm 中的内容

c# - 为什么我的日期验证器不起作用?

javascript - ASP.NET C# <asp :Button type ="button"> but still acting like <asp:Button type ="submit">

javascript - VS 2013 WPF Web浏览器启用Javascript