wpfinotifydataerrorinfo

How to properly check 2 related data fields using INotifyDataErrorInfo on WPF form?


I got 2 string data fields on a form. My rule is the value of the 2 data fields cannot be the same and not empty. It seems it does work well to check the rule because of data changed is not raised again until the value is changed again. Ex: F1 = F2 empty string ==> Invalid data. F1 = 1, F2 =2 ==> Pass. F1 = 2; F2 = 2 ==> F1 invalid (red rectangle). F2= 3 ==> F1 still having red rectangle because data is not changed so it will not evaluate again although F1 <> F2 on the screen but the error message is still F1 = F2. If I change F1 to something else and back to 2 then it is OK because it has been evaluated again. I ended up checking the F1 and F2 data field when user tries to save the form. How would you handle this scenario? Thanks


Solution

  • using Simplified;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    
    namespace Core2023.SO.ThichCoiPhim
    {
        public class TwoFieldsVM : ViewModelBase, INotifyDataErrorInfo
        {
            public string Field1 { get => Get<string>(); set => Set(value); }
            public string Field2 { get => Get<string>(); set => Set(value); }
            public bool HasErrors => !Errors.All(pair => string.IsNullOrEmpty(pair.Value));
    
            public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
    
            public IEnumerable GetErrors(string? propertyName)
            {
                if (string.IsNullOrEmpty(propertyName))
                {
                    foreach (var pair in Errors)
                    {
                        if (!string.IsNullOrEmpty(pair.Value))
                            yield return $"{pair.Key}: {pair.Value}";
                    }
                }
                else if (Errors.TryGetValue(propertyName, out var errors))
                {
                    if (!string.IsNullOrEmpty(errors))
                        yield return $"{propertyName}: {errors}";
                }
            }
    
            private readonly Dictionary<string, string> Errors = new()
            {
                { nameof(Field1), Field1EmptyError },
                { nameof(Field2), Field2EmptyError },
            };
            private const string Field1EmptyError = $"{nameof(Field1)} Empty";
            private const string Field2EmptyError = $"{nameof(Field2)} Empty";
            private const string Field1EqualsField2Error = $"{nameof(Field1)} Equals {nameof(Field2)}";
    
            protected override void OnPropertyChanged(string propertyName, object? oldValue, object? newValue)
            {
                base.OnPropertyChanged(propertyName, oldValue, newValue);
    
                if (propertyName is nameof(Field1) or nameof(Field2))
                {
                    bool f1 = string.IsNullOrEmpty(Field1);
                    bool f2 = string.IsNullOrEmpty(Field2);
    
                    string fe1 = Errors[nameof(Field1)];
                    string fe2 = Errors[nameof(Field2)];
    
                    if (f1 || f2)
                    {
                        Errors[nameof(Field1)] = f1 ? Field1EmptyError : string.Empty;
                        Errors[nameof(Field2)] = f2 ? Field2EmptyError : string.Empty;
                    }
                    else if (Field1 == Field2)
                    {
                        Errors[nameof(Field1)] = Field1EqualsField2Error;
                        Errors[nameof(Field2)] = string.Empty;
                    }
                    else
                    {
                        Errors[nameof(Field1)] = string.Empty;
                        Errors[nameof(Field2)] = string.Empty;
                    }
                    if (fe1 != Errors[nameof(Field1)])
                    {
                        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Field1)));
                    }
                    if (fe2 != Errors[nameof(Field2)])
                    {
                        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Field2)));
                    }
                }
            }
        }
    }
    
    <Window x:Class="Core2023.SO.ThichCoiPhim.TwoFieldsWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Core2023.SO.ThichCoiPhim"
            mc:Ignorable="d"
            Title="TwoFieldsWindow" Height="450" Width="800">
        <Window.DataContext>
            <local:TwoFieldsVM/>
        </Window.DataContext>
        <UniformGrid>
            <TextBox x:Name="textBox1" Text="{Binding Field1, UpdateSourceTrigger=PropertyChanged}" Margin="10"/>
            <TextBox x:Name="textBox2" Text="{Binding Field2, UpdateSourceTrigger=PropertyChanged}" Margin="10"/>
            <ItemsControl ItemsSource="{Binding Path=(Validation.Errors), ElementName=textBox1, Mode=OneWay}"
                          DisplayMemberPath="ErrorContent"/>
            <ItemsControl ItemsSource="{Binding Path=(Validation.Errors), ElementName=textBox2, Mode=OneWay}"
                          DisplayMemberPath="ErrorContent"/>
        </UniformGrid>
    </Window>