简体   繁体   中英

WPF UI controls not validating correctly on ErrorsChanged event

I have the following INotifyDataErrorInfo implementation in an abstract base 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));
            });
        }
    }

In models deriving from the base class I implement the Task<ValidationResult> GetValidationResultAsync() required method, it uses fluent validation Nuget package.

    private readonly ModelValidator _modelValidator = new ModelValidator();

    protected override Task<ValidationResult> GetValidationResultAsync()
    {
        return _modelValidator.ValidateAsync(this);
    }

The problem is that when I invoke from a ViewModel the ValidateAsync() method of a model the UI input controls are not invalidate/validate correctly, I actually have a tab control and validate the models in tab index changed, some might show the red border once I change tab but then again return to normal state to the next tab change.

In debug it shows that the ValidationErrors property returns errors.

My XAML input controls code is like below.

    <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>

[Update 1]

I should mention that I use in the MainWindow a tab control and 3 tab items, each tab item is a UserControl.

I hooked up to the Validation.Error event of all the XAML UserControls and I noticed that even I get tab selected index changed value the Validation.Error fires once for the first tab and never again, I suspect there is a cleanup somewhere for a reason.

Code for the SelectedTabIndex that fires the models validations.

        private int _selectedTabIndex = 0;
        public int SelectedTabIndex
        {
            get { return _selectedTabIndex; }
            set
            {
                _selectedTabIndex = value;
                ValidateModels();
                Tab2ViewModel.ValidateModels();
                Tab3ViewModel.ValidateModels();
                OnPropertyChanged();
            }
        }

The ValidateModels method calls ValidateAsync of the model in the ViewModel.

        public override Task ValidateModels()
        {
            return Model.ValidateAsync();
        }

MainWindow TabControl XAML.

<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">

[Update 2]

After adding a custom error style and a custom error template, I see that the controls tooltip stay with the condition not met error but the error template is clearing. So, the TextBox shows no error template, custom or default, but the validation error exists and the tooltip shows the error.

Why the XAML templates clear on TabIndexChange and how come they don't refresh at least on the active tab item I'm viewing. This might be the problem that I should solve.

Also, as mentioned before, I don't see the ErrorsChanged revalidating the controls except the first time the SelectedTabIndex setter is invoked.

The templates I added.

<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>

The problem is that tabs, expanders etc don't work well with validators, you need to include AdornerDecorator , or not use tabs which in my case is not an option.

Issue with WPF validation(IDataErrorInfo) and tab focusing .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM