dotnet icon indicating copy to clipboard operation
dotnet copied to clipboard

[Question] Red border always visible

Open grizoood opened this issue 4 years ago • 4 comments

Hello, I have a problem with validations. The validation border is still present after performing the validation of the property. I work with WPF

I create a course list (with 2 properties: start and end). For each race I pass the course list as a parameter.

Course class :

    public class Course : ObservableValidator
    {
        #region Fields

        private IEnumerable<Course> _courses;

        private int _start;

        private int _end;

        #endregion

        #region Properties

        [CustomValidation(typeof(Course), nameof(ValidateStart))]
        public int Start
        {
            get { return _start; }
            set
            {
                SetProperty(ref this._start, value, true);
                // ValidateProperty(this.End, nameof(End));
                ValidateCourses();
            }
        }

        [CustomValidation(typeof(Course), nameof(ValidateEnd))]
        public int End
        {
            get { return _end; }
            set
            {
                SetProperty(ref this._end, value, true);
                // ValidateProperty(this.Start, nameof(Start));
                ValidateCourses();
            }
        }

        #endregion

        #region Constructors

        public Course(IEnumerable<Course> courses)
        {
            _courses = courses;
                ValidateCourses();
        }

        #endregion

        #region Methods

        private void ValidateCourses()
        {
            foreach (var course in _courses)
                course.ValidateAllProperties();
        }

        #endregion

        #region Validations

        public static ValidationResult ValidateStart(int start, ValidationContext context)
        {
            Course course = (Course)context.ObjectInstance;

            List<Course> courses = course._courses.ToList();

            List<string> results = new List<string>();

            int index = courses.IndexOf(course);

            if (index == 0 && start != 0)
                results.Add("Start must be equal to zero");

            if (start >= course.End)
                results.Add("Start must be less end");

            if (index != -1 && index - 1 != -1)
                if (start != courses[index - 1].End)
                    results.Add("Start must be equal previous end");

            if (results.Count == 0)
            {
                return ValidationResult.Success;
            }

            return new ValidationResult(string.Join("\n", results), new List<string>() { nameof(Course.Start) });
        }

        public static ValidationResult ValidateEnd(int end, ValidationContext context)
        {
            Course course = (Course)context.ObjectInstance;

            List<Course> courses = course._courses.ToList();

            List<string> results = new List<string>();

            int index = courses.IndexOf(course);

            if (course.Start >= end)
                results.Add("End must be greater Start");

            if (index + 1 <= courses.Count - 1)
                if (end != courses[index + 1].Start)
                    results.Add("End must be equal next Start");

            if (results.Count == 0)
            {
                return ValidationResult.Success;
            }

            return new ValidationResult(string.Join("\n", results), new List<string>() { nameof(Course.End) });
        }

        #endregion
    }

MainViewModel class :

 public class MainViewModel : ObservableObject
    {
        #region Fields

        private ObservableCollection<Course> _courses = new ObservableCollection<Course>();

        #endregion

        #region Properties

        public ObservableCollection<Course> Courses
        {
            get { return _courses; }
            set { _courses = value; }
        }

        #endregion

        #region Constructors

        public MainViewModel()
        {
            CreateCourses();
        }

        #endregion

        #region Methods

        private void CreateCourses()
        {
            Courses.Add(new Course(Courses)
            {
                Start = 0,
                End = 10
            });
            Courses.Add(new Course(Courses)
            {
                Start = 11,
                End = 20
            });
            Courses.Add(new Course(Courses)
            {
                Start = 21,
                End = 30
            });
            Courses.Add(new Course(Courses)
            {
                Start = 31,
                End = 30
            });
        }

        #endregion
    }

MainWindow class

<DataGrid ItemsSource="{Binding Courses, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                  AutoGenerateColumns="False">
            <DataGrid.Resources>
                <Style x:Key="errorStyle" TargetType="{x:Type TextBlock}">
                    <Style.Triggers>
                        <Trigger Property="Validation.HasError" Value="True">
                            <Setter Property="ToolTip" 
                                    Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Start TextColumn"
                                    ElementStyle="{StaticResource errorStyle}"
                                    Binding="{Binding Start, ValidatesOnExceptions=True}"/>
                <DataGridTextColumn Header="End TextColumn"
                                    ElementStyle="{StaticResource errorStyle}"
                                    Binding="{Binding End, ValidatesOnExceptions=True}"/>

                <DataGridTemplateColumn Header="Start TemplateColumn (TextBox)">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Start, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">
                                <TextBox.Style>
                                    <Style TargetType="TextBox">
                                        <Style.Triggers>
                                            <Trigger Property="Validation.HasError" Value="true">
                                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="End TemplateColumn (TextBox)">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding End, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">
                                <TextBox.Style>
                                    <Style TargetType="TextBox">
                                        <Style.Triggers>
                                            <Trigger Property="Validation.HasError" Value="true">
                                                <Setter Property="Validation.ErrorTemplate">
                                                    <Setter.Value>
                                                        <ControlTemplate>
                                                            <AdornedElementPlaceholder/>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

When I edit a property, I check all the properties in the course list.

My problem is that the border still appears even though the property returned ValidationResult.Success

we can see that in the image below

image

I attach the test project. ProjetTest.zip

Please can you help me

grizoood avatar Dec 01 '21 14:12 grizoood

Hello grizoood, thank you for your interest in Windows Community Toolkit!

I have automatically added a "needs triage" label to help get things started. Our team will analyze and investigate the issue, and escalate it to the relevant team if possible.. Other community members may also answer the question and provide feedback 🙌

ghost avatar Dec 01 '21 14:12 ghost

Thanks for providing more details @grizoood, didn't see this initially when I responded to your discussion here.

Going to transfer this to the .NET Community Toolkit repo where the MVVM Toolkit lives. FYI @Sergio0694

michael-hawker avatar Dec 01 '21 23:12 michael-hawker

Hi @grizoood! Would you be able to provide us a minimal repro? That would help us a lot to investigate this. By that I mean the minimum possible amount of code that can reproduce the issue (eg. literally just a single control in XAML with just a line for a binding, and the smallest viewmodel you can get, with like a single property and the minimum amount of validation possible, no comments, no extra code, etc.). Additionally, does the issue only happen with that custom setter you have, or can you also repro with eg. a TextBlock binding its text? Thanks! 🙂

Sergio0694 avatar Dec 02 '21 00:12 Sergio0694

Sorry for the late reply, I put the issue on hold.

Validation works if I add a null style to hide cell validation:

Validation.ErrorTemplate" Value="{x:Null}"
<DataGridTemplateColumn.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Style.Resources>
            <Style TargetType="{x:Type ContentPresenter}">
                <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
            </Style>
        </Style.Resources>
    </Style>
</DataGridTemplateColumn.CellStyle>

Before: I have 2 borders (DataGridCell and TextBox) image

Après: I have 1 border (TextBox) image

<DataGridTemplateColumn Header="Start DataGridTemplateColumn (TextBox)">
    <DataGridTemplateColumn.CellStyle>
        <Style TargetType="{x:Type DataGridCell}">
            <Style.Resources>
                <Style TargetType="{x:Type ContentPresenter}">
                    <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
                </Style>
            </Style.Resources>
        </Style>
    </DataGridTemplateColumn.CellStyle>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Start, UpdateSourceTrigger=PropertyChanged}" Margin="5">
                <TextBox.Style>
                    <Style TargetType="TextBox">
                        <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="true">
                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)/ErrorContent, RelativeSource={RelativeSource Self}}"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBox.Style>
            </TextBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

I didn't have the problem with the DataGridTextColumn because I was directly modifying the cell style of the DataGridTextColumn but not for the DataGridTemplateColumn.

grizoood avatar Feb 02 '22 12:02 grizoood