Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation error popup on TextBox briefly flashes at the top of screen #4404

Open
Reeceeboii opened this issue May 29, 2023 · 0 comments
Open
Labels

Comments

@Reeceeboii
Copy link

Reeceeboii commented May 29, 2023

Describe the bug

When using data validation against a user-entered value via a TextBox with UpdateSourceTrigger=PropertyChanged, I have noticed that whenever the user enters an incorrect value, the error popup will actually appear briefly at the top of the screen before returning to the correct place besides the offending TextBox.

This looks extremely similar, if not identical, to #4120. But confusingly, I get the same behaviour even if I remove ValidatesOnExceptions=True.

Another issue, #3809, which is for a different bug, however there was a screenshot included in the replies that perfectly demonstrates what is occurring for me. However, here is a video recorded from my machine. This occurs when running in Debug and Release.

Recording.2023-05-29.040656.mp4

Steps to reproduce

  1. Have a model for data validation that implements INotifyDataErrorInfo:
    public abstract class ValidationModelBase : INotifyDataErrorInfo
    {
        private readonly IDictionary<string, List<string>> propertyErrors;

        protected ValidationModelBase()
        {
            this.propertyErrors = new Dictionary<string, List<string>>();
        }

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public bool HasErrors => this.propertyErrors.Any();

        public bool IsValid => !this.HasErrors;

        public IEnumerable GetErrors(string propertyName)
        {
            if (propertyName == null)
            {
                return null;
            }

            return this.propertyErrors.TryGetValue(propertyName, out List<string> errors) ? errors : null;
        }

        public void AddErrors(string propertyName, string message)
        {
            if (!this.propertyErrors.ContainsKey(propertyName))
            {
                this.propertyErrors.Add(propertyName, new List<string>());
            }

            this.propertyErrors[propertyName].Add(message);
            this.RaiseErrorsChangedEvent(propertyName);
        }

        public void RemoveErrors(string propertyName)
        {
            if (this.propertyErrors.Remove(propertyName))
            {
                this.RaiseErrorsChangedEvent(propertyName);
            }
        }

        private void RaiseErrorsChangedEvent(string propertyName)
        {
            this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
    public class Router : ValidationModelBase
    {
        private string routerIpv4Address;

        public string RouterIpv4Address
        {
            get => this.routerIpv4Address;
            set
            {
                if (value != this.routerIpv4Address)
                {
                    this.RemoveErrors(nameof(this.RouterIpv4Address));
                    if (!NetworkService.VerifyIpv4Address(value)) // hardcode true/false here to mimic actual verification for the purpose of duplication
                    {
                        this.AddErrors(nameof(this.RouterIpv4Address), "Not a valid IPv4 value");
                    }
                    this.routerIpv4Address = value;
                } 
            }
        }
    }
  1. Use this model as the backing field for a property you intend to validate inside a ViewModel:
    public class RouterSetupViewModel : ObservableObject, INotifyDataErrorInfo
    {
        private readonly Router router;

        public RouterSetupViewModel()
        {
            this.router = new Router();
            this.router.ErrorsChanged += this.RouterErrorsChanged;
        }

        public bool HasErrors => this.router.HasErrors;

        public IEnumerable GetErrors(string propertyName)
        {
            return this.router.GetErrors(propertyName);
        }

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public string RouterIpv4Address
        {
            get => this.router.RouterIpv4Address;
            set {
                this.router.RouterIpv4Address = value;
                this.OnPropertyChanged();
            }
        }

        private void RouterErrorsChanged(object sender, DataErrorsChangedEventArgs e)
        {
            this.ErrorsChanged?.Invoke(this, e);
        }
    }
  1. Bind to property in view:
<TextBox
    Text="{ Binding RouterIpv4Address, 
    UpdateSourceTrigger=PropertyChanged,
    NotifyOnValidationError=True,
    ValidatesOnExceptions=True,
    Delay=400}"
    mah:TextBoxHelper.ClearTextButton="True"
    mah:TextBoxHelper.UseFloatingWatermark="True"
    mah:TextBoxHelper.Watermark="Router's IP address"/>

Expected behavior

The error only appears alongside the TextBox.

Actual behavior

The error briefly flashes at the top of the screen.

Environment

MahApps.Metro version: 2.4.9
Windows build number: Win11 22H2 [Version 22621.1778]
Visual Studio Enterprise 2022 17.6.2
Target Framework: .NET Framework 4.8

Screenshots

Video included above.

@Reeceeboii Reeceeboii added the Bug label May 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

1 participant