将DataAnnotations和IDataErrorInfo结合使用于WPF

5

我正在编写一个WPF应用程序,我想使用数据注释来指定像Required字段、Range等内容。

我的ViewModel类使用常规的INotifyPropertyChanged接口,我可以很容易地使用C# 4 Validator验证整个对象,但如果它们不能正确验证,我也想让这些字段变成红色。我在这里找到了这篇博客文章(http://blogs.microsoft.co.il/blogs/tomershamam/archive/2010/10/28/wpf-data-validation-using-net-data-annotations-part-ii.aspx),它讲述了如何编写您的基本视图模型以实现IDataErrorInfo并简单地使用验证器,但是实现实际上无法编译,我也看不出它将如何工作。有问题的方法是:

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

问题在于没有提供GetValue。他可能在谈论继承DependencyObject时所带来的GetValue,但语法仍然无法正常工作(它期望您将DependencyProperty作为参数传递),但我使用普通CLR属性,并在setter上调用了OnPropertyChanged("MyProperty")
有没有好的方法将验证连接到IDataErrorInfo接口?
2个回答

5

以您上面的代码为起点,我通过IDataErrorInfo解决了这个问题。

您的问题集中在当您只有属性名称时获取属性值,反射可以帮助解决此问题。

public string this[string property]
{
   get
   {
      PropertyInfo propertyInfo = this.GetType().GetProperty(property);
      var results = new List<ValidationResult>();

      var result = Validator.TryValidateProperty(
                                propertyInfo.GetValue(this, null),
                                new ValidationContext(this, null, null)
                                {
                                  MemberName = property
                                }, 
                                results);

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

      return string.Empty;
   }
}

这基本上就是我最终所做的事情,但如果您在视图模型中有依赖属性,它将无法正常工作,因此您必须尝试两种方式。 这不像我希望的那样优美,但有时这就是你必须去尝试的。 - Nate Zaugg

1
我知道这篇文章很旧,但是最近在参考这篇文章的帮助下解决了这个问题,并进行了一些优化。我想分享我的ViewModelBase对IDataErrorInfo的实现。它使用编译表达式来获取属性值,从而加快了属性值的访问速度。当类型加载到内存中时,我还会在后台线程上启动表达式编译。希望在第一次调用OnValidate之前完成编译,因为表达式编译可能会有点慢。感谢和祝福。
public abstract class ViewModelBase<TViewModel> : IDataErrorInfo
    where TViewModel : ViewModelBase<TViewModel>
{
    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, propertyGetters.Result[propertyName]((TViewModel)this)); } 
    }

    private static Task<Dictionary<string, Func<TViewModel, object>>> propertyGetters = Task.Run(() =>
    {
        return typeof(TViewModel).GetProperties()
            .Select(propertyInfo =>
            {
                var viewModel = Expression.Parameter(typeof(TViewModel));
                var property = Expression.Property(viewModel, propertyInfo);
                var castToObject = Expression.Convert(property, typeof(object));
                var lambda = Expression.Lambda(castToObject, viewModel);

                return new
                {
                    Key = propertyInfo.Name,
                    Value = (Func<TViewModel, object>)lambda.Compile()
                };
            })
            .ToDictionary(pair => pair.Key, pair => pair.Value);
    });

    protected virtual string OnValidate(string propertyName, object propertyValue)
    {
        var validationResults = new List<ValidationResult>();

        var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName };

        if (!Validator.TryValidateProperty(propertyValue, validationContext, validationResults))
        {
            return validationResults.First().ErrorMessage;
        }

        return string.Empty;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接