用 INotifyDataErrorInfo 替换 IDataErrorInfo

Replacing IDataErrorInfo with INotifyDataErrorInfo

我有一个名为 Person 的 class 有两个属性 FirstNameLastName, 两个 Constructors、一个 ICommand 和一些常用的东西INotifyPropertyChangedIDataErrorInfo 需要:

class Person : ObservableCollection<Person>, INotifyPropertyChanged, IDataErrorInfo
{
    string firstName, lastName;

    #region Properties
    [Required(ErrorMessage = "First Name is Required")]
    [RegularExpression("test", ErrorMessage = "It's to be test")]
    public string FirstName {
        get => firstName;
        set { firstName = value; OnPropertyChanged(); }
    }

    [Required]
    [RegularExpression("test", ErrorMessage = "It also has to be test")]
    public string LastName {
        get => lastName;
        set { lastName = value; OnPropertyChanged(); }
    }
    #endregion Properties
   
    #region Constructors
    public Person(){
        AddToList = new Command(CanAdd, Add);
    }

    public Person(string fName, string lName){
        FirstName = fName;
        LastName = lName;
    }
    #endregion Constructors

    #region Command
    public ICommand AddToList { get; set; }
    bool CanAdd(object para) => Validator.TryValidateObject(this, new ValidationContext(this), null, true);
    void Add(object para){
        Add(new Person(FirstName, LastName));
        FirstName = LastName = null;
    }
    #endregion Command

    #region INotifyPropertyChanged
    public new event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    #endregion INotifyPropertyChanged

    #region IDataErrorInfo
    public string Error => null;
    public string this[string columnName] {
        get {
            var ValidationResults = new List<ValidationResult>();
            if (Validator.TryValidateProperty(
                    GetType().GetProperty(columnName).GetValue(this),
                    new ValidationContext(this) { MemberName = columnName },
                    ValidationResults
                )) return null;

            return ValidationResults.First().ErrorMessage;
        }
    }
    #endregion IDataErrorInfo
}

in xaml 我有两个 TextBox 绑定到 Person 的 FirstNameLastName,两个 Label 用于验证错误消息和一个 Button,绑定到ICommand,在下面的ListView中添加Person:

<Window ...>
    <Window.Resources>
        <local:Person x:Key="Person"/>
    </Window.Resources>

    <Grid DataContext="{StaticResource Person}">
        <StackPanel>
            <TextBox x:Name="Fname" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
            <Label Content="{Binding (Validation.Errors)[0].ErrorContent, ElementName=Fname}"/>

            <TextBox x:Name="Lname" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
            <Label Content="{Binding (Validation.Errors).CurrentItem.ErrorContent, ElementName=Lname}"/>
        
            <Button Content="Click" Command="{Binding AddToList}" />
        
            <ListView x:Name="lv" ItemsSource="{Binding}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="First Name" Width="200" 
                            DisplayMemberBinding="{Binding FirstName}"/>
                        <GridViewColumn Header="Last Name" Width="200" 
                            DisplayMemberBinding="{Binding LastName}" />
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
    </Grid>
</Window>

有效,如果名字和姓氏无效,我会收到错误消息,只要名字和姓氏中的任何一个无效,按钮就会保持禁用状态。我只想用 INotifyDataErrorInfo 替换 IDataErrorInfo 部分。我必须在 Person class 和 xaml 中进行哪些更改才能保持相同的功能?

每次引发 ErrorsChanged 事件时,框架都会调用 GetErrors。因为有一个 HasErrors 属性 应该 return true 只要有任何验证错误,在 属性 设置器中验证并缓存验证是有意义的Dictionary<string, List<ValidationResult>>.

中的错误

请参考以下示例实现:

class Person : ObservableCollection<Person>, INotifyPropertyChanged, INotifyDataErrorInfo
{
    string firstName, lastName;

    #region Properties
    [Required(ErrorMessage = "First Name is Required")]
    [RegularExpression("test", ErrorMessage = "It's to be test")]
    public string FirstName
    {
        get => firstName;
        set { firstName = value; OnPropertyChanged(); Validate(); }
    }

    [Required]
    [RegularExpression("test", ErrorMessage = "It also has to be test")]
    public string LastName
    {
        get => lastName;
        set { lastName = value; OnPropertyChanged(); Validate(); }
    }
    #endregion Properties

    #region Constructors
    public Person()
    {
        AddToList = new Command(CanAdd, Add);
        Validate(nameof(FirstName));
        Validate(nameof(LastName));
    }

    public Person(string fName, string lName)
    {
        FirstName = fName;
        LastName = lName;
    }
    #endregion Constructors

    #region Command
    public ICommand AddToList { get; set; }
    bool CanAdd(object para) => _validationResults.Count == 0;
    void Add(object para)
    {
        base.Add(new Person(FirstName, LastName));
        FirstName = LastName = null;
    }
    #endregion Command

    #region INotifyPropertyChanged
    public new event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    #endregion INotifyPropertyChanged

    #region INotifyDataErrorInfo
    private readonly Dictionary<string, List<ValidationResult>> _validationResults = new Dictionary<string, List<ValidationResult>>();

    public bool HasErrors => _validationResults.Count > 0;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if (_validationResults.TryGetValue(propertyName, out List<ValidationResult> validationResults))
            return new string[1] { validationResults.First().ErrorMessage };
        return null;
    }

    private void Validate([CallerMemberName]string propertyName = "")
    {
        var ValidationResults = new List<ValidationResult>();
        if (Validator.TryValidateProperty(typeof(Person).GetProperty(propertyName).GetValue(this),
                new ValidationContext(this) { MemberName = propertyName }, ValidationResults))
        {
            _validationResults.Remove(propertyName);
        }
        else
        {
            _validationResults[propertyName] = ValidationResults;
        }
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }
    #endregion
}

不知道它是否好,但如果我只是用以下内容替换 IDataErrorInfo 区域:

#region IDataErrorInfo
public bool HasErrors => true;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
    var ValidationResults = new List<ValidationResult>();
    if (Validator.TryValidateProperty(GetType().GetProperty(propertyName).GetValue(this),
            new ValidationContext(this) { MemberName = propertyName }, ValidationResults))
        return null;

    return new string[1] { ValidationResults.First().ErrorMessage };
}
#endregion IDataErrorInfo

其他部位不要碰,也可以!