Создайте задержку перед тем, как ошибки проверки будут представлены пользователю - PullRequest
4 голосов
/ 26 июня 2011

В этом конкретном приложении WPF, которое следует шаблону MVVM, модель представления реализует интерфейс IDataErrorInfo для уведомления о представлении недопустимых данных в текстовых полях.

В представлении существует текстовое поле, в которое можно ввестиобъем.Это было указано при изменении свойства источника обновления и проверки ошибок данных:

<TextBox 
    Text="{Binding Volume, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

Проблема заключается в том, что вы получаете ошибку проверки до того, как пользователь закончит ввод.Например, действительным значением является «25 мл».Но перед тем, как пользователь наберет последний «l», в текстовом поле присутствует «25 m».Это недопустимое значение, в результате чего реализация IDataError скажет:

В результате, когда пользователь печатает, вокруг текстового поля мигает красный маркер.

Нам хотелось быиметь небольшую задержку (0,5 с) до появления красного маркера вокруг текстового поля, поэтому мы можем предположить, что пользователь закончил печатать до появления ошибок проверки.

Первой попыткой было создание специализированного текстового поля.это ждет 0,5 секунды перед обновлением свойства в модели представления.Но это нехорошо, потому что в случае, если пользователь вводит правильное значение, затем проходит 0,5 секунды, прежде чем кнопка подтверждения будет включена.

У меня есть идея, что вы могли бы написать специализированное связывание (т.е.создать специализированный класс, производный от System.Windows.Data.Binding), который реализует это поведение, но я понятия не имею, как это сделать.

Это правдоподобный способ или есть лучший?

Ответы [ 2 ]

2 голосов
/ 26 июня 2011

Похоже, вы могли бы использовать пользовательскую привязку DelayBinding, о которой писал Пол Стовелл. Я использовал его с большим успехом, реализовав отложенный поиск / фильтрацию. Вы можете прочитать об этом здесь:

http://www.paulstovell.com/wpf-delaybinding

1 голос
/ 28 января 2019

Я искал то же самое и не нашел решения, поэтому я что-то построил сам.Я хотел отложить проверку, но не отложить установку свойства.Поэтому я сделал это с помощью таймеров и INotifyDataErrorInfo, который позволяет выполнять асинхронные проверки и уведомления о событиях.

Далее я улучшил его, так как при вводе ошибки валидации удаляются немедленно и через секунду после ввода ошибки снова отображаются.

public abstract class NotifyDataErrorInfoViewModelBase : ViewModelBase, INotifyDataErrorInfo
{
    private ConcurrentDictionary<string, List<ValidationResult>> modelErrors = new ConcurrentDictionary<string, List<ValidationResult>>();
    private ConcurrentDictionary<string, Timer> modelTimers = new ConcurrentDictionary<string, Timer>();

    public bool HasErrors { get => modelErrors.Any(); }

    public IEnumerable GetErrors(string propertyName)
    {
        modelErrors.TryGetValue(propertyName, out var propertyErrors);
        return propertyErrors;
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    protected NotifyDataErrorInfoViewModelBase() : base()
    { PropertyChanged += (s, e) => Validate(e.PropertyName); }

    private void NotifyErrorsChanged([CallerMemberName] string propertyName = "")
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    private void Validate([CallerMemberName] string propertyName = "")
    {
        var timer = modelTimers.AddOrUpdate(propertyName, new Timer(), (key, existingTimer) => { existingTimer.Stop(); return new Timer(); });
        timer.Interval = 1000;
        timer.AutoReset = false;
        modelErrors.TryRemove(propertyName, out var existingErrors);  // clear existing errors immediately
        if (existingErrors?.Count > 0)
            NotifyErrorsChanged(propertyName);
        timer.Elapsed += (s, e) => CheckForErrors(propertyName, existingErrors);
        timer.Start();
    }

    private async void CheckForErrors(string propertyName)
    {
        await Task.Factory.StartNew(() =>
        {
            var errorMessage = "";
            try
            {
                errorMessage = GetValidationMessage(propertyName);
            }
            catch (Exception ex) { errorMessage = "strValidationError"; }
            if (string.IsNullOrEmpty(errorMessage))
            {
                if (existingErrors?.Count > 0)
                    NotifyErrorsChanged(propertyName);
            }
            else
            {
                modelErrors[propertyName] = new List<ValidationResult> { new ValidationResult(errorMessage) };
                NotifyErrorsChanged(propertyName);
            }
        });
    }

    private string GetValidationMessage(string propertyName)
    {
        var property = GetType().GetProperty(propertyName).GetValue(this);
        var validationContext = new ValidationContext(this) { MemberName = propertyName };
        var validationResults = new List<ValidationResult>();
        if (!Validator.TryValidateProperty(property, validationContext, validationResults) && validationResults.Count > 0)
        {
            var messages = new List<string>();
            foreach (var validationResult in validationResults)
            {
                messages.Add(validationResult.ErrorMessage);
            }
            var message = string.Join(Environment.NewLine + "\u25c9 ", messages);
            if (messages.Count > 1)
                message = "\u25c9 " + message;  // add bullet point
            return message;
        }
        return null;
    }
}

Я использую его с GalaSoft.MvvmLight, но я уверен, что вы могли бы использовать что-то другое (или вообще не использовать ViewModelBase).

Функция Validate ("variableName ") запускает проверку (здесь задержка в 1 секунду), в моем случае я прикрепил ее к событию PropertyChanged, но вы также можете вместо этого вызвать Validate () в установщике свойств, если хотите.

Я использую его вместе с этим, чтобы показать валидацию в пользовательском интерфейсе WPF: https://stackoverflow.com/a/20394432/9758687

Редактировать: В качестве альтернативы часть WPF может быть отложена также с использованием анимации без использованиятаймеры выше.Преимущество в том, что проверка выполняется немедленно, и это полезно, например, для отключения кнопок, если проверка не прошла успешно.Вот код, который я использую в своем ErrorTemplate:

<Style.Triggers>
    <Trigger Property="IsVisible" Value="True">
        <Trigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation BeginTime="0:0:0.8" Duration="0:0:0.5" To="1.0" Storyboard.TargetProperty="Opacity" />
                </Storyboard>
            </BeginStoryboard>
        </Trigger.EnterActions>
    </Trigger>
</Style.Triggers>
...