我有一个ObservableCollection类型的数据收集(例如,实例为myClassTypes)。在执行一些用户操作之后,此myClassTypes用ViewModel中的值填充。在视图中,有一个TextBox,用户可以在其中输入文本。我需要根据myClassTypes值验证文本框数据。因此,如果myClassTypes包含用户在文本框中插入的文本,则将通过验证,否则它将失败。 我的代码段是: ViewModel:
public ObservableCollection < MyClassType > ViewModelClassTypes {
get {
return _myClassTypes;
}
set {
_myClassTypes = value;
NotifyOfPropertyChange(() = >MyClassTypes);
}
}
public class TestValidationRule: ValidationRule {
public ObservableCollection < MyClassType > MyClassTypes {
get = >(ObservableCollection < MyClassType > ) GetValue(MyClassTypesProperty);
set = >SetValue(MyClassTypesProperty, value);
}
}
仅供参考:MyClassTypesProperty是依赖项属性
我的View.xaml是:
<TextBox>
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationRules:TestValidationRule MyClassTypes="{Binding ViewModelClassTypes}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
我无法在MyClassTypes中获得ViewModelClassTypes填充的值。有人可以建议我在做什么错吗?
答案 0 :(得分:0)
自.Net 4.5开始实施验证的首选方法是让您的视图模型实施INotifyDataErrorInfo
MSDN。 Example。 Example from MSDN (Silverlight)。注意:INotifyDataErrorInfo
取代了过时的IDataErrorInfo
。
实现INotifyDataErrorInfo
时,必须提供一个HasErrors
属性,您可以从视图中绑定该属性以显示错误消息。另外,我们还必须引入一个属性作为TextBox
的绑定目标到视图模型。我们将其称为UserInput
。
INotifyDataErrorInfo
的工作方式当ValidatesOnNotifyDataErrors
的{{1}}属性设置为Binding
时,绑定引擎将在绑定源上搜索true
实现以订阅{{1 }}事件。
如果引发INotifyDataErrorInfo
事件并且ErrorsChanged
的值为ErrorsChanged
,则绑定将为实际属性调用HasErrors
方法,以检索特定的错误消息并应用可定制的验证错误模板以可视化该错误。默认情况下,未通过验证的元素周围会绘制红色边框。
true
视图模型负责验证其属性。因此,它必须实现GetErrors()
。关键是每个属性都有单独的INotifyDataErrorInfo
。
视图模型可能如下所示:
INotifyDataErrorInfo
The ValidationRule
:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
public ViewModel()
{
this.errors = new Dictionary<string, List<string>>();
this.validationRules = new Dictionary<string, List<ValidationRule>>();
this.validationRules.Add(nameof(this.UserInput), new List<ValidationRule>() { new TestValidationRule() });
}
public bool ValidateProperty(object value, [CallerMemberName] string propertyName = null)
{
lock (this.syncLock)
{
if (!this.validationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
return;
}
// Clear previous errors of the current property to be validated
if (this.errors.ContainsKey(propertyName))
{
this.errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
// Apply all the rules that are associated with the current property
propertyValidationRules.ForEach(
(validationRule) =>
{
ValidationResult result = validationRule.Validate(value, CultuteInfo.CurrentCulture);
if (!result.IsValid)
{
AddError(propertyName, result.ErrorContent, false);
}
}
}
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if 'isWarning' is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
RaiseErrorsChanged(propertyName);
}
}
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName)) return null;
return this.errors[propertyName];
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
#endregion
#region INotifyDataErrorInfo Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private MyClassType _myClassTypes;
public ObservableCollection<MyClassType> ViewModelClassTypes
{
get
{
return this._myClassTypes;
}
set
{
this._myClassTypes = value;
if (!this.validationRules.TryGetValue(nameof(this.UserInput), out List<ValidationRule> userInputValidationRules))
{
TestValidationRule userInputRule = userInputValidationRules.Where((rule) => rule is TestValidationRule).FirstOrDefault();
userInputRule.MyClassTypes = this._myClassTypes;
}
OnPropertyChanged();;
}
}
private double userInput;
public double UserInput
{
get => userInput;
set
{
if (ValidateProperty(value))
{
this.userInput = value;
OnPropertyChanged();
}
}
}
private Dictionary<String, List<String>> errors;
// The ValidationRules for each property
private Dictionary<String, List<ValidationRule>> validationRules;
private object syncLock = new object();
}
视图:
ValidationRule
以下是验证错误模板,以防您要自定义外观(可选)。通过附加的属性public class TestValidationRule : ValidationRule
{
public TestValidationRule()
{
this.MyClassTypes = new List<MyClassType>();
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!value is string userInput)
{
throw new InvalidArgumentException();
}
// Replace with your validation implementation
if (this.MyClassTypes.Contains(userInput))
{
return ValidationResult.ValidResult;
}
else
{
return new ValidationResult(false,
"MyClassType doesn't exist.");
}
}
public List<MyClassType> MyClassTypes { get; set; }
}
(在上文中),在已验证元素(在本例中为<Grid>
<Grid.DataContext>
<ViewModel x:Name="ViewModel" />
</Grid.DataContext>
<TextBox x:Name="Input" Text = "{Binding UserInput, Mode=OneWayToSource, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</Grid>
)上进行了设置:
TextBox
在我提供的链接旁边,您会在网络上找到许多示例。
我建议将Validation.ErrorTemplate
的实现转移到基类(例如<ControlTemplate x:Key=ValidationErrorTemplate>
<StackPanel>
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
INotifyPropertyChanged`中,并让所有视图模型都继承它。这使验证逻辑可重用,并保持视图模型类整洁。
您可以更改INotifyDataErrorInfo
的实现细节以符合要求。
备注:该代码未经测试。这些代码片段应该可以使用,但是旨在提供一个示例,说明如何实现BaseViewModel) together with
接口。