Замена IDataErrorInfo на INotifyDataErrorInfo - PullRequest
0 голосов
/ 22 октября 2019

У меня есть класс с именем Person с двумя свойствами FirstName, LastName, два Constructors, один ICommand и обычные вещи, необходимые для INotifyPropertyChanged и IDataErrorInfo:

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, привязанные к FirstName и LastName Person, два Label для сообщения об ошибке проверки и Button, привязанные к ICommand, для добавления Personв следующем ListView:

<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>

Это работает, я получаю сообщение об ошибке, если имя и фамилия не действительны, а кнопка остается отключенной, пока любое из имени и фамилии является недействительным. Я хочу заменить только деталь IDataErrorInfo на INotifyDataErrorInfo. Какие изменения необходимо внести в Person класс и xaml, чтобы сохранить те же функции?

Ответы [ 2 ]

1 голос
/ 23 октября 2019

Фреймворк вызывает GetErrors каждый раз, когда вы вызываете событие ErrorsChanged. Поскольку существует свойство HasErrors, которое должно возвращать 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
}
0 голосов
/ 24 октября 2019

Не знаю, хорошо это или нет, НО, если я просто заменю 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

и не касаюсь других частей, это также работает!

...