MahApps / MahApps/MahApps.Metro

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

Open
#4,404 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug
Dominant language
C#
Stars
9.8k
Forks
2.4k
Avg merge
1h 2m
Merged PRs (30d)
56

Description

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.

https://github.com/MahApps/MahApps.Metro/assets/42159320/1057965a-e999-4930-a445-469c81996316

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the supplied TextBox validation binding with INotifyDataErrorInfo on the stated .NET Framework 4.8 and Windows environment, then trace the validation error popup's positioning relative to the offending TextBox. Done means the error remains alongside the TextBox without briefly appearing at the top of the screen in both Debug and Release.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
desktop, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.