我是WPF的新手,我对验证器感兴趣,似乎你需要继承ValidationRule并覆盖Validate函数,这完全与视图模型分离,但如果我想验证怎么办?针对viewmodel中的某些list / collection / set / dictionary,要检查这个新输入是否已经在列表中,一个很好的例子是创建验证以查看是否尚未使用用户名。
答案 0 :(得分:0)
在WPF中进行验证有几种不同的方法。我能想到两个主要的方法
验证规则在XAML(GUI)中指定,而实现IDataErrorInfo则将验证逻辑移动到ViewModel(业务逻辑)中。虽然ValidationRules很好,因为您可以创建自己的并重用它们,但它们也无法在您的业务逻辑中提供最有可能需要的验证。
客户端与服务器端验证的概念很有意思,可能与Silverlight有关,但由于您将其标记为WPF,我假设唯一的区别是验证是在Views还是ViewModels(UI或商业逻辑)。在我看来,即使您的UI验证输入,您的ViewModel仍然需要进行适当的验证。
因此,我建议实现IDataErrorInfo。顺便说一下,IDataErrorInfo工作的原因是因为存在检查IDataErrorInfo接口的ValidationRule!以下是我将如何在ViewModelBase类中执行此操作的示例:
注意:以下示例忽略了您可能需要通知INotifyPropertyChanged以更新绑定的事实,而只关注验证。
public class ViewModelBase : IDataErrorInfo
{
private Dictionary<string, string> errors = new Dictionary<string, string>();
// required for IDataErrorInfo
public virtual string Error
{
get { return String.Join(Environment.NewLine, errors); }
}
// required for IDataErrorInfo
public string this[string propertyName]
{
get
{
string result;
errors.TryGetValue(propertyName, out result);
return result;
}
}
// Useful property to check if you have errors
public bool HasErrors
{
get
{
return errors.Count > 0;
}
}
protected void SetError<T>(string propertyName, String error)
{
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(string propertyName, String error)
{
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
protected virtual void OnHasErrorsChanged()
{
}
}
然后您的ViewModel可以像这样实现它:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError("SomeProperty", ValidateSomeProperty());
}
}
}
private string ValidateSomeProperty()
{
if(String.IsNullOrEmpty(SomeProperty))
return "Value is required";
return null;
}
}
在你的用户界面中,你需要像这样添加ValidatesOnDataErrors和NotifyOnValidationError:
Text="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
注意:传入字符串来表示属性有点难看(如果重命名属性但忘记重命名字符串,则重构不安全)。当您想要通知DataBindings的属性更改时,INotifyPropertyChanged的方式相同。 Prism的NotificationObject有一个重构安全的解决方案,它看起来像这样:
使用以下代码替换上一个示例中的GetError / SetError:
protected void SetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
if (error == null)
errors.Remove(propertyName);
else
errors[propertyName] = error;
OnHasErrorsChanged();
}
protected string GetError<T>(Expression<Func<T>> prop, String error)
{
String propertyName = PropertySupport.ExtractPropertyName(prop);
String s;
errors.TryGetValue(propertyName, out s);
return s;
}
然后我的ViewModelBase是这样的:
public class ViewModelBase : NotificationObject, IDataErrorInfo
然后实现属性:
public class MyViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get
{
return someProperty;
}
set
{
if(someProperty != null)
{
someProperty = value;
SetError( () => SomeProperty, ValidateSomeProperty()); // update validation for property
RaisePropertyChanged( () => SomeProperty); // notify data bindings
}
}
}
我没有展示RaisePropertyChanged的实现,但它是在Prism的NotificationObject中,它是开源的,可以免费下载。你可以自己实现INotifyPropertyChanged并用字符串引发事件(不是重构安全)或者自己实现它类似于上面的SetError实现(提取属性名并用它激活事件)。
你需要这个辅助方法:
public static class PropertySupport
{
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
{
if (propertyExpresssion == null)
{
throw new ArgumentNullException("propertyExpression");
}
var memberExpression = propertyExpresssion.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
}
return memberExpression.Member.Name;
}
}
编辑:简化的重构安全替代
如果您使用的是.NET 4.5或更高版本,则可以使用CallerMemberAttribute之类的this example shows来实现INotifyPropertyChanged和我的SetError实现。那么你不需要通过上面的反射自己提取属性名称(它简化了很多)。
很抱歉,如果您希望DataBindings和Validation能够正常运行,那么它们可以与我们讨论物业变更通知,但它们是相辅相成的。
答案 1 :(得分:-1)
我终于做到了,这就是:
的Xaml:
<Window.Resources>
<l:DataResource x:Key="ValidateFieldMethod" BindingTarget="{Binding IsFieldValid}"/>
</Window.Resources>
<xctk:IntegerUpDown Width="50" Maximum="300" Minimum="0">
<xctk:IntegerUpDown.Value>
<Binding Path="SelectedItem.TargetPosition" Mode="TwoWay">
<Binding.ValidationRules>
<l:CustomValidationRule Validator="{l:DataResourceBinding DataResource={StaticResource ValidateFieldMethod}}" />
</Binding.ValidationRules>
</Binding>
</xctk:IntegerUpDown.Value>
</xctk:IntegerUpDown>
ValidatorRule:
public delegate bool CheckAgainstDataDelegate(object newValue, string fieldName);
public class CustomValidationRule : ValidationRule
{
public CheckAgainstDataDelegate Validator { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingGroup owner)
{
return Validate((object)owner, cultureInfo);
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
switch (ValidationStep)
{
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = (object)owner;
break;
}
return new ValidationResult(Validator(value, ((BindingExpression) owner).ResolvedSourcePropertyName), null);
}
[Obsolete("Use Validator property of type delegate instead to validate the data",true)]
public override ValidationResult Validate(object value, CultureInfo cultureInfo) { return null; } //not used anymore
}
在viewModel中:
private CheckAgainstDataDelegate _isFieldValid;
public CheckAgainstDataDelegate IsFieldValid
{
get
{
return _isFieldValid
?? (_isFieldValid = delegate (object newValue,string propertyName)
{
switch (propertyName)
{
case "TargetPosition":
var newV = (int) newValue;
return Items.All(e => e.TargetPosition != newV);
default:
throw new Exception("Property Assigned to unknown field");
}
});
}
}
我使用http://www.wpfmentor.com/2009/01/how-to-add-binding-to-property-on.html的帮助在ValidationrRule中绑定。
你怎么想?