为 WPF 组合 DataAnnotations 和 IDataErrorInfo

发布于 2024-11-29 11:18:15 字数 1854 浏览 3 评论 0原文

我正在编写一个 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 接口?

I am writing a WPF application and I want to use Data Annotations to specify things like Required Fields, Range, etc.

My ViewModel classes use the regular INotifyPropertyChanged interface and I can validate the entire object easily enough using the C# 4 Validator, but I would also like the fields to highlight red if they do not validate properly. I found this blog post here (http://blogs.microsoft.co.il/blogs/tomershamam/archive/2010/10/28/wpf-data-validation-using-net-data-annotations-part-ii.aspx) that talks about how to write your base view model to implement IDataErrorInfo and simply use the Validator, but the implementation doesn't actually compile nor can I see how it would work. The method in question is this:

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

The problem is GetValue is not provided. He could be talking about the GetValue that comes when you inherit DependencyObject, but the syntax still doesn't work (it expects you to pass DependencyProperty as a parameter) but I'm using regular CLR properties with OnPropertyChanged("MyProperty") being invoked on the setter.

Is there a good way to connect the validation to the IDataErrorInfo interface?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

日久见人心 2024-12-06 11:18:16

使用上面的代码作为起点,我通过 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;
   }
}

Using your above code as a starting point I got this working through IDataErrorInfo.

Your problem centred around getting the value of the property when you only have the property name, reflection can help here.

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;
   }
}
假情假意假温柔 2024-12-06 11:18:16

我知道这篇文章很旧,但我最近在这篇文章的帮助下解决了这个问题,同时做了一些优化。我想分享我的 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;
    }
}

I know this post is old, but I recently solved this problem with help from this post, while making some optimizations along the way. I'd like to share my ViewModelBase's implementation of IDataErrorInfo. It uses compiled expressions for the property getters which speeds the property value access. I also fire off the expression compilations on background thread when the type is loaded into memory. Hopefully, it finishes compilation before the first call to OnValidate since expression compilation can be a bit slow. Thanks and cheers.

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文