WPF UI 控件未正确验证 ErrorsChanged 事件
WPF UI controls not validating correctly on ErrorsChanged event
我在抽象基础中有以下 INotifyDataErrorInfo 实现 class。
private IEnumerable<ValidationErrorModel> _validationErrors = new List<ValidationErrorModel>();
public IEnumerable<ValidationErrorModel> ValidationErrors
{
get { return _validationErrors; }
private set
{
_validationErrors = value;
OnPropertyChanged();
}
}
protected abstract Task<ValidationResult> GetValidationResultAsync();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
ValidationErrors == null)
return null;
IEnumerable<string> errors = ValidationErrors
.Where(p => p.PropertyName.Equals(propertyName))
.Select(p => p.ToString())
.ToList();
return errors;
}
public bool HasErrors
{
get
{
bool hasErrors = ValidationErrors != null && ValidationErrors.Any();
return hasErrors;
}
}
public Task<ValidationResult> ValidateAsync()
{
Task<ValidationResult> validationResultTask = GetValidationResultAsync();
validationResultTask.ContinueWith((antecedent) =>
{
if (antecedent.IsCompleted &&
!antecedent.IsCanceled &&
!antecedent.IsFaulted)
{
ValidationResult validationResult = antecedent.Result;
if (validationResult != null)
{
lock (ValidationErrors)
{
ValidationErrors =
validationResult.Errors
.Select(validationFailure =>
new ValidationErrorModel(validationFailure.PropertyName, validationFailure.ErrorMessage))
.ToList();
foreach (ValidationErrorModel validationErrorModel in ValidationErrors)
{
RaiseErrorsChanged(validationErrorModel.PropertyName);
}
}
}
}
});
return validationResultTask;
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected virtual void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
Dispatcher.InvokeOnMainThread(() =>
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
});
}
}
在从基础派生的模型中 class 我实现了 Task<ValidationResult> GetValidationResultAsync()
所需的方法,它使用流畅的验证 Nuget 包。
private readonly ModelValidator _modelValidator = new ModelValidator();
protected override Task<ValidationResult> GetValidationResultAsync()
{
return _modelValidator.ValidateAsync(this);
}
问题是,当我从 ViewModel 调用模型的 ValidateAsync()
方法时,UI 输入控件不 invalidate/validate 正确,我实际上有一个选项卡控件并验证选项卡索引中的模型已更改,有些可能会在我更改选项卡后显示红色边框,但随后再次 return 到下一个选项卡更改的正常状态。
在调试中显示 ValidationErrors
属性 return 错误。
我的 XAML 输入控制代码如下。
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scheduled Date:"/>
<DatePicker DisplayDate="{Binding ScheduledDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
</StackPanel>
</StackPanel>
</Grid>
[更新 1]
我应该提一下,我在 MainWindow
中使用了一个选项卡控件和 3 个选项卡项,每个选项卡项都是一个 UserControl。
我连接到所有 XAML UserControls 的 Validation.Error 事件,我注意到即使我得到选项卡选择的索引更改值 Validation.Error 也会为第一个选项卡触发一次并且再也没有了,我怀疑某处出于某种原因进行了清理。
触发模型验证的 SelectedTabIndex 代码。
private int _selectedTabIndex = 0;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
_selectedTabIndex = value;
ValidateModels();
Tab2ViewModel.ValidateModels();
Tab3ViewModel.ValidateModels();
OnPropertyChanged();
}
}
ValidateModels
方法调用 ViewModel 中模型的 ValidateAsync
。
public override Task ValidateModels()
{
return Model.ValidateAsync();
}
主窗口选项卡控件XAML。
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
[更新 2]
添加自定义错误样式和自定义错误模板后,我看到控件工具提示仍然显示条件未满足错误,但错误模板正在清除。因此,TextBox 不显示任何错误模板(自定义或默认),但存在验证错误并且工具提示显示错误。
为什么 XAML 模板在 TabIndexChange 上清除,为什么它们至少不刷新我正在查看的活动选项卡项目。这可能是我应该解决的问题。
此外,如前所述,除了第一次调用 SelectedTabIndex setter 之外,我没有看到 ErrorsChanged 重新验证控件。
我添加的模板。
<Application.Resources>
<Style x:Key="ErrorStyle"
TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Contains Invalid Data"
Width="10"
Height="10"
>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0"/>
<GradientStop Color="#FFFF0000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
问题是制表符、扩展器等不能很好地与验证器一起使用,您需要包含 AdornerDecorator
,或者不使用制表符,在我的情况下这不是一个选项。
Issue with WPF validation(IDataErrorInfo) and tab focusing.
我在抽象基础中有以下 INotifyDataErrorInfo 实现 class。
private IEnumerable<ValidationErrorModel> _validationErrors = new List<ValidationErrorModel>();
public IEnumerable<ValidationErrorModel> ValidationErrors
{
get { return _validationErrors; }
private set
{
_validationErrors = value;
OnPropertyChanged();
}
}
protected abstract Task<ValidationResult> GetValidationResultAsync();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
ValidationErrors == null)
return null;
IEnumerable<string> errors = ValidationErrors
.Where(p => p.PropertyName.Equals(propertyName))
.Select(p => p.ToString())
.ToList();
return errors;
}
public bool HasErrors
{
get
{
bool hasErrors = ValidationErrors != null && ValidationErrors.Any();
return hasErrors;
}
}
public Task<ValidationResult> ValidateAsync()
{
Task<ValidationResult> validationResultTask = GetValidationResultAsync();
validationResultTask.ContinueWith((antecedent) =>
{
if (antecedent.IsCompleted &&
!antecedent.IsCanceled &&
!antecedent.IsFaulted)
{
ValidationResult validationResult = antecedent.Result;
if (validationResult != null)
{
lock (ValidationErrors)
{
ValidationErrors =
validationResult.Errors
.Select(validationFailure =>
new ValidationErrorModel(validationFailure.PropertyName, validationFailure.ErrorMessage))
.ToList();
foreach (ValidationErrorModel validationErrorModel in ValidationErrors)
{
RaiseErrorsChanged(validationErrorModel.PropertyName);
}
}
}
}
});
return validationResultTask;
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected virtual void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
Dispatcher.InvokeOnMainThread(() =>
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
});
}
}
在从基础派生的模型中 class 我实现了 Task<ValidationResult> GetValidationResultAsync()
所需的方法,它使用流畅的验证 Nuget 包。
private readonly ModelValidator _modelValidator = new ModelValidator();
protected override Task<ValidationResult> GetValidationResultAsync()
{
return _modelValidator.ValidateAsync(this);
}
问题是,当我从 ViewModel 调用模型的 ValidateAsync()
方法时,UI 输入控件不 invalidate/validate 正确,我实际上有一个选项卡控件并验证选项卡索引中的模型已更改,有些可能会在我更改选项卡后显示红色边框,但随后再次 return 到下一个选项卡更改的正常状态。
在调试中显示 ValidationErrors
属性 return 错误。
我的 XAML 输入控制代码如下。
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scheduled Date:"/>
<DatePicker DisplayDate="{Binding ScheduledDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
</StackPanel>
</StackPanel>
</Grid>
[更新 1]
我应该提一下,我在 MainWindow
中使用了一个选项卡控件和 3 个选项卡项,每个选项卡项都是一个 UserControl。
我连接到所有 XAML UserControls 的 Validation.Error 事件,我注意到即使我得到选项卡选择的索引更改值 Validation.Error 也会为第一个选项卡触发一次并且再也没有了,我怀疑某处出于某种原因进行了清理。
触发模型验证的 SelectedTabIndex 代码。
private int _selectedTabIndex = 0;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
_selectedTabIndex = value;
ValidateModels();
Tab2ViewModel.ValidateModels();
Tab3ViewModel.ValidateModels();
OnPropertyChanged();
}
}
ValidateModels
方法调用 ViewModel 中模型的 ValidateAsync
。
public override Task ValidateModels()
{
return Model.ValidateAsync();
}
主窗口选项卡控件XAML。
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
[更新 2]
添加自定义错误样式和自定义错误模板后,我看到控件工具提示仍然显示条件未满足错误,但错误模板正在清除。因此,TextBox 不显示任何错误模板(自定义或默认),但存在验证错误并且工具提示显示错误。
为什么 XAML 模板在 TabIndexChange 上清除,为什么它们至少不刷新我正在查看的活动选项卡项目。这可能是我应该解决的问题。
此外,如前所述,除了第一次调用 SelectedTabIndex setter 之外,我没有看到 ErrorsChanged 重新验证控件。
我添加的模板。
<Application.Resources>
<Style x:Key="ErrorStyle"
TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Contains Invalid Data"
Width="10"
Height="10"
>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0"/>
<GradientStop Color="#FFFF0000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
问题是制表符、扩展器等不能很好地与验证器一起使用,您需要包含 AdornerDecorator
,或者不使用制表符,在我的情况下这不是一个选项。
Issue with WPF validation(IDataErrorInfo) and tab focusing.