源BLog https://kmatyaszek.github.io/wpf%20validation/2019/03/06/wpf-validation-using-idataerrorinfo.html
    示例工程 https://github.com/JustMySpace/DemoCollections.WPF/tree/main/%E5%B1%9E%E6%80%A7%E9%AA%8C%E8%AF%81/INotifyDataErrorInfo%E5%AE%9E%E7%8E%B0%E5%B1%9E%E6%80%A7%E9%AA%8C%E8%AF%81

    优点在于支持复杂的验证逻辑,但是要与Attribute 如 MaxLength、Required等结合需要自己补充一些逻辑,且在现有的Mvvm框架中较难以动态代理的方式添加这些逻辑。

    INotifyDataErrorInfo是.NET 4.5中增加的一个接口,可是实现自定义的规则验证并反馈结果到界面,该接口包含三个接口:

    • HasErrors 标识是否有验证错误
    • GetErrors 返回IEnumerable类型的错误集合,当propertyName 不为空时,返回特定属性的验证错误,当propertyName 为空字符串或Null时,戴白哦整个实体的验证错误
    • ErrorsChanged 当验证错误发生变化时,必须触发这个事件

    一个简单的示例
    界面:

    1. <TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">
    2. <Validation.ErrorTemplate>
    3. <ControlTemplate>
    4. <StackPanel>
    5. <AdornedElementPlaceholder x:Name="textBox" />
    6. <ItemsControl ItemsSource="{Binding}">
    7. <ItemsControl.ItemTemplate>
    8. <DataTemplate>
    9. <TextBlock Text="{Binding ErrorContent}" Foreground="Red" />
    10. </DataTemplate>
    11. </ItemsControl.ItemTemplate>
    12. </ItemsControl>
    13. </StackPanel>
    14. </ControlTemplate>
    15. </Validation.ErrorTemplate>
    16. </TextBox>

    VM:

    1. public class MainViewModel : BindableBase, INotifyDataErrorInfo
    2. {
    3. private string _userName;
    4. private readonly Dictionary<string, List<string>> _errorsByPropertyName = new Dictionary<string, List<string>>();
    5. public MainViewModel()
    6. {
    7. UserName = null;
    8. }
    9. public string UserName
    10. {
    11. get => _userName;
    12. set
    13. {
    14. _userName = value;
    15. ValidateUserName();
    16. RaisePropertyChanged();
    17. }
    18. }
    19. public bool HasErrors => _errorsByPropertyName.Any();
    20. public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    21. public IEnumerable GetErrors(string propertyName)
    22. {
    23. return _errorsByPropertyName.ContainsKey(propertyName) ?
    24. _errorsByPropertyName[propertyName] : null;
    25. }
    26. private void OnErrorsChanged(string propertyName)
    27. {
    28. ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    29. }
    30. private void ValidateUserName()
    31. {
    32. ClearErrors(nameof(UserName));
    33. if (string.IsNullOrWhiteSpace(UserName))
    34. AddError(nameof(UserName), "Username cannot be empty.");
    35. if (string.Equals(UserName, "Admin", StringComparison.OrdinalIgnoreCase))
    36. AddError(nameof(UserName), "Admin is not valid username.");
    37. if (UserName == null || UserName?.Length <= 5)
    38. AddError(nameof(UserName), "Username must be at least 6 characters long.");
    39. }
    40. private void AddError(string propertyName, string error)
    41. {
    42. if (!_errorsByPropertyName.ContainsKey(propertyName))
    43. _errorsByPropertyName[propertyName] = new List<string>();
    44. if (!_errorsByPropertyName[propertyName].Contains(error))
    45. {
    46. _errorsByPropertyName[propertyName].Add(error);
    47. OnErrorsChanged(propertyName);
    48. }
    49. }
    50. private void ClearErrors(string propertyName)
    51. {
    52. if (_errorsByPropertyName.ContainsKey(propertyName))
    53. {
    54. _errorsByPropertyName.Remove(propertyName);
    55. OnErrorsChanged(propertyName);
    56. }
    57. }
    58. }