Я искал то же самое и не нашел решения, поэтому я что-то построил сам.Я хотел отложить проверку, но не отложить установку свойства.Поэтому я сделал это с помощью таймеров и 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>