为WPF组合DataAnnotations和IDataErrorInfo

时间:2011-08-15 22:34:25

标签: wpf data-annotations idataerrorinfo

我正在编写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并简单地使用Validator,但实现实际上并没有编译,也无法看到它是如何工作的。有问题的方法是:

    /// <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未提供。他可能正在讨论继承GetValue时出现的DependencyObject,但语法仍无效(它希望您将DependencyProperty作为参数传递)但我正在使用在setter上调用OnPropertyChanged("MyProperty")的常规CLR属性。

有没有一种方法可以将验证连接到IDataErrorInfo界面?

2 个答案:

答案 0 :(得分: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;
   }
}

答案 1 :(得分:1)

我知道这篇文章已经过时了,但我最近在这篇文章的帮助下解决了这个问题,同时还在进行一些优化。我想分享我的ViewModelBase的IDataErrorInfo实现。它使用属性getter的编译表达式来加速属性值访问。当类型加载到内存中时,我还会在后台线程上触发表达式编译。希望它在第一次调用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;
    }
}