我正在尝试实现 INotifyDataErrorInfo 并且我的模型有一些自定义类型,这些类型需要根据它们的使用进行不同的验证。我不确定如何实施此验证。
我尝试在下面创建一个简单的示例,以显示我要完成的工作。我不是在寻找有关更改模型的建议,因为我的实际模型要复杂得多。
简单示例
我的示例模型适用于将有演示者和嘉宾的媒体活动。安排媒体活动时,用户将输入姓名、最少和最多演示者以及最少和最多来宾。通常,媒体必须至少有1名主持人且不超过5名,并且必须至少有10名嘉宾且不超过50名。
我有以下课程,取自在线示例,用作我的模型类的基础。
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace NotifyDataErrorInfo
{
public class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
public ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
ValidateAsync();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
}
public IEnumerable GetErrors(string propertyName)
{
if (propertyName == null) return null;
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get
{
return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0);
}
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
}
}
因为我在两个地方使用了最小值和最大值,所以我创建了以下类来存储最小值和最大值。这是我的例子中过于简单的部分,但应该明白这一点。
namespace NotifyDataErrorInfo
{
public class MinMaxValues : ValidatableModel
{
private int min;
private int max;
public int Min
{
get
{
return min;
}
set
{
if (!min.Equals(value))
{
min = value;
RaisePropertyChanged(nameof(Min));
OnErrorsChanged(nameof(Min));
}
}
}
public int Max
{
get
{
return max;
}
set
{
if (!max.Equals(value))
{
max = value;
RaisePropertyChanged(nameof(Max));
OnErrorsChanged(nameof(Max));
}
}
}
public MinMaxValues()
{
Min = 0;
Max = 0;
}
}
}
这是我的 MediaEvent 类,您可以看到它为 MinMaxPresenters 和 MinMaxGuests 使用了 MinMaxValues 类。
using System.ComponentModel.DataAnnotations;
namespace NotifyDataErrorInfo
{
public class MediaEvent: ValidatableModel
{
private string name;
private MinMaxValues minMaxPresenters;
private MinMaxValues minMaxGuests;
public MediaEvent()
{
name = string.Empty;
minMaxPresenters = new MinMaxValues();
minMaxGuests = new MinMaxValues();
this.Validate();
this.minMaxPresenters.Validate();
this.minMaxGuests.Validate(); }
}
[Required]
[StringLength(10, MinimumLength = 5)]
public string Name
{
get
{
return name;
}
set
{
if(!name.Equals(value))
{
name = value;
RaisePropertyChanged(nameof(Name));
}
}
}
public MinMaxValues MinMaxPresenters
{
get
{
return minMaxPresenters;
}
set
{
if (!minMaxPresenters.Equals(value))
{
minMaxPresenters = value;
RaisePropertyChanged(nameof(MinMaxPresenters));
}
}
}
public MinMaxValues MinMaxGuests
{
get
{
return minMaxGuests;
}
set
{
if (!minMaxGuests.Equals(value))
{
minMaxGuests = value;
RaisePropertyChanged(nameof(MinMaxGuests));
}
}
}
}
}
这是我的 MainWindow 的 XAML
<Window
x:Class="NotifyDataErrorInfo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NotifyDataErrorInfo"
mc:Ignorable="d"
Title="MainWindow"
Height="209" Width="525"
ResizeMode="NoResize">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="42*"/>
<RowDefinition Height="43*"/>
<RowDefinition Height="42*"/>
<RowDefinition Height="43*"/>
</Grid.RowDefinitions>
<Label
Content="Meeting Name: "
Grid.Row="0" Grid.Column="0"/>
<TextBox
Text="{Binding Name}"
Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3"/>
<Label
Content="Min Presenters: "
Grid.Row="1" Grid.Column="0"/>
<TextBox
Text="{Binding MinMaxPresenters.Min}"
Grid.Row="1" Grid.Column="1"/>
<Label
Content="Max Presenters: "
Grid.Row="1" Grid.Column="2"/>
<TextBox
Text="{Binding MinMaxPresenters.Max}"
Grid.Row="1" Grid.Column="3"/>
<Label
Content="Min Guests: "
Grid.Row="2" Grid.Column="0"/>
<TextBox
Text="{Binding MinMaxGuests.Min}"
Grid.Row="2" Grid.Column="1"/>
<Label
Content="Max Guests: "
Grid.Row="2" Grid.Column="2"/>
<TextBox
Text="{Binding MinMaxGuests.Max}"
Grid.Row="2" Grid.Column="3"/>
<Button
x:Name="TestButton"
Content="TEST"
Click="TestButton_Click"
Grid.Row="3" Grid.Column="3"/>
</Grid>
</Window>
在 App.xaml.cs 中使用
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow();
var mediaEvent = new MediaEvent();
mainWindow.DataContext = mediaEvent;
mainWindow.Show();
}
在 MediaEvent 类中,我使用 [Required] 和 [StringLength(10, MinimumLength = 5)] 属性修饰了 Name 属性。这些按预期工作。当输入的名称短于 5 个字符或长于 10 个字符时,我可以在名称文本框周围看到一个红色框,表明存在错误。
什么我想不通
现在我不确定如何验证 MinMaxPresenters.Min、MinMaxPresenters.Max、MinMaxGuests.Min 和 MinMaxGuests.Max
如果我用 [Range(1, 5)] 之类的东西装饰 MinMaxValues 类中的 Min 属性,我可以确认验证正在发生并且 UI 会相应地更新。
问题是验证适用于演示者和来宾的最小值。我需要为演示者和来宾验证不同的最小值。
我试过的
在 MediaEvent 中,我加入了 minMaxPresenters 的 PropertyChanged 事件。在那个事件处理程序中,我尝试根据演示者的规则(范围 = 1 到 5)验证 Min 和 Max 值。如果验证失败,我尝试添加到 _errors 集合。
在我的构造函数中,我添加了
minMaxPresenters.PropertyChanged += MinMaxPresenters_PropertyChanged;
然后创建了以下
private void MinMaxPresenters_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Min")
{
if (minMaxPresenters.Min < 1)
{
_errors.TryAdd("MinMaxPresenters.Min", new List<string> { "A media event requires at least 1 presenter" });
OnErrorsChanged("MinMaxPresenters.Min");
}
}
else if (e.PropertyName == "Max")
{
if (minMaxPresenters.Max <= minMaxPresenters.Min)
{
_errors.TryAdd("MinMaxPresenters.Max", new List<string> { "The max presenters must be greater than the min" });
OnErrorsChanged("MinMaxPresenters.Max");
}
else if (minMaxPresenters.Max > 5)
{
_errors.TryAdd("MinMaxPresenters.Max", new List<string> { "A media event can't have more than 5 presenters" });
OnErrorsChanged("MinMaxPresenters.Max");
}
}
}
当我输入超出演示者范围的最小值和最大值时,我可以看到我的错误被添加到模型中的 _errors 集合中,但我的视图并未表明存在任何错误。
我接近了吗?我对这一切都错了吗?
我还需要根据其他属性值验证值,因此需要进行自定义验证并通过代码添加错误。一个例子是验证上面的最大值。演示者的最大值需要小于 5,但也必须大于为最小值输入的值。
编辑
您可以忽略 MainWindow 中的按钮。只需单击并中断后面的代码,这样我就可以看到集合中有什么错误。
此外,如果有人评论公开 _errors,这只是尝试添加错误的快速方法。理想情况下,我会创建 AddError 和 RemoveError 方法。