【问题标题】:Validation firing too early验证触发太早
【发布时间】:2011-11-01 13:22:58
【问题描述】:

我已经为我的视图模型构建了一个基类。以下是一些代码:

public class BaseViewModel<TModel> : DependencyObject, INotifyPropertyChanged, IDisposable, IBaseViewModel<TModel>, IDataErrorInfo
{
        public TModel Model { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this._disposed)
            {
                return;
            }

            if (disposing)
            {
                this.Model = default(TModel);
            }

            this._disposed = true;
        }
}

好的,所以我想,让我们为基类添加一些验证,这导致我阅读了以下文章:Prism IDataErrorInfo validation with DataAnnotation on ViewModel Entities。所以我在我的基类中添加了以下方法/属性(IDataErrorInfo):

string IDataErrorInfo.Error
{
    get { return null; }
}

string IDataErrorInfo.this[string columnName]
{
    get { return ValidateProperty(columnName); }
}

protected virtual string ValidateProperty(string columnName)
{
    // get cached property accessors
    var propertyGetters = GetPropertyGetterLookups(GetType());

    if (propertyGetters.ContainsKey(columnName))
    {
        // read value of given property
        var value = propertyGetters[columnName](this);

        // run validation
        var results = new List<ValidationResult>();
        var vc = new ValidationContext(this, null, null) { MemberName = columnName };
        Validator.TryValidateProperty(value, vc, results);

        // transpose results
        var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
        return string.Join(Environment.NewLine, errors);
    }
    return string.Empty;
}

private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
{
    var key = objType.FullName ?? "";
    if (!PropertyLookupCache.ContainsKey(key))
    {
        var o = objType.GetProperties()
        .Where(p => GetValidations(p).Length != 0)
        .ToDictionary(p => p.Name, CreatePropertyGetter);

        PropertyLookupCache[key] = o;
        return o;
    }
    return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
}

private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
{
    var instanceParameter = System.Linq.Expressions.Expression.Parameter(typeof(object), "instance");

    var expression = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(
        System.Linq.Expressions.Expression.ConvertChecked(
            System.Linq.Expressions.Expression.MakeMemberAccess(
                System.Linq.Expressions.Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
                propertyInfo),
            typeof(object)),
        instanceParameter);

    var compiledExpression = expression.Compile();

    return compiledExpression;
}

private static ValidationAttribute[] GetValidations(PropertyInfo property)
{
    return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}

好的,这让我想到了这个问题。问题是验证工作完美,但可以说我有一个带有 StringLength 属性的属性(在我的视图模型中:Person)。 StringLength 属性会在应用程序打开后立即触发。用户甚至没有机会做任何事情。应用程序启动后验证会立即触发。

public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
    private string _password = string.Empty;
    [StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
    public string Password
    {
        get { return this._password; }
        set
        {
            if (this._password != value)
            {
                this._password = value;
                this.OnPropertyChanged("Password");
            }
        }
    }
}

我注意到这是由IDataErrorInfo.this[string columnName] 属性引起的,而它又调用了ValidateProperty 方法。但是,我不知道如何解决这个问题?

【问题讨论】:

  • 呃,为什么不跳过第一次调用它?
  • 我怎么知道这是第一次?我无法在我的基类中对其进行硬编码。
  • 你怎么知道这是第一次?您跟踪它就像跟踪所有其他状态一样。您可以通过第一次跳过 OnPropertyChanged for Password 来防止它被触发。

标签: wpf c#-4.0 mvvm


【解决方案1】:

可能有两个问题...

您是否使用公共属性填充 yopur Person 实例?

例如

  new Person { Password = null }

这将触发密码的属性更改通知并对其进行验证。

有些开发者还在构造函数中设置属性...

public class Person {
   public Person() {
      this.Password = null;
   }
} 

推荐的做法是使用私有字段...

public class Person {
   public Person() {
      _password = null;
   }

   public Person(string pwd) {
      _password = pwd;
   }
} 

您可以在我们的视图模型库中创建一个标志,例如 IsLoaded。确保仅在加载 UI 后将其设置为 true(可能在 UI.Loaded 事件中)。在您的IDataErrorInfo.this[string columnName] 中检查此属性是否为真,然后才验证这些值。否则返回null。

[编辑]

以下更改完成了这项工作:

public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
    private string _password;
    [StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
    public string Password
    {
        get { return this._password; }
        set
        {
            if (this._password != value)
            {
                this._password = value;
                this.OnPropertyChanged("Password");
            }
        }
    }

    public PersonViewModel(BaseProxyWrapper<PosServiceClient> model)
        : base(model)
    {
        this._username = null;
    }
}

【讨论】:

  • 嗨,关于第一个建议,我使用了以下 CTOR 方法(是的,我使用了公共属性,但使用了封装):PersonView pView = new PersonView(); PersonViewModel pViewModel = new PersonViewModel(client); pView.DataContext = pViewModel; 那么“密码”的值为通过绑定框架设置:&lt;TextBox Grid.Row="0" Grid.Column="3" Margin="2" Width="120" Text="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}" /&gt; 只有当用户输入一个值时才会在Passwordproperty 上触发。
  • 其次,我考虑过IsLoaded 属性,但感觉就像对基类进行硬编码。如果下一个开发者忘记处理UI.Loaded 事件会怎样?
  • 好的,我知道你从哪里来了......将提交我的答案是帖子......所以,无法回答我自己的问题......代表太低了?!所以我会在 cmets 发帖!
  • @Richard:请不要尝试向 cmets 添加代码。如果 Angel 的回答不充分,您可以提交带有附加信息的 edit。或者您可以选择正确。
【解决方案2】:

我过去做过的事情是将更新源触发器更改为显式,创建一个在 TextBox 失去焦点时更新源的行为,然后将该行为附加到 TextBox。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多