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.