我一直在寻找相同的解决方案,但没有找到解决方案,所以我自己构建了一些东西。我想延迟验证但不延迟设置属性。所以我用计时器和允许异步检查和事件通知的 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 UI 中显示验证: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>