1

I have a textbox that prevents the user from writing the unallowed characters that I've specified with regex, and if the user writes any of them, a popup shows up and stays there for 5 seconds, and I made this duration using the Task.Delay(5000, cts.Token) which has a cancellation token as well, and while the popup is on the screen if the user writes an allowed character, then the popup disappears, and that delay task gets canceled and disposed, but if I write an unallowed char, and then immediately write an allowed char, and if I do it really fast, then when I write an unallowed char to open the popup, the popup disappears in less than 5 seconds (in a random second, that I think it's because the delay task gets canceled at that moment and then causes the popup to disappear), which is the same problem as when I didn't use the cancellation tokens to cancel the delay task. But with the codes that I've written, I don't know how the task doesn't get canceled completely, or maybe it does but the problem is something else ...

    CancellationTokenSource cts;
    protected async override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        if (txtName.IsFocused)
        {
            if (cts != null && !regex.IsMatch(e.Text))
            {
                cts.Cancel();
                cts.Dispose();
            }

            cts = new CancellationTokenSource();

            if (regex.IsMatch(e.Text))
            {
                e.Handled = true;

                txtName.CaretIndex = txtName.Text.Length;

                if (AlertPopup.IsOpen == false)
                {
                    AlertPopup.IsOpen = true;
                    txtName.Focus();
                    try
                    {
                        await Task.Delay(5000, cts.Token);
                    }
                    catch (TaskCanceledException) { }
                    finally
                    {
                        AlertPopup.IsOpen = false;
                    }
                }
            }
            else
            {
                AlertPopup.IsOpen = false;
            }

            base.OnPreviewTextInput(e);
        }
    }
bzmind
  • 386
  • 3
  • 19

1 Answers1

1

The OnPreviewTextInput() method is only ever executed in the UI thread. Likewise, the continuation after the await in that method is only ever executed in the UI thread. The UI thread can only do one thing at a time. This means that in the case where the error popup is already displayed, when there is new user input and the code attempts to cancel that display (i.e. by canceling the delay's cancel token), the await that subsequently results in the AlertPopup.IsOpen property getting set to false does not actually execute the continuation until the new call to OnPreviewTextInput() has returned.

So, when there's new user input, that input is either valid or invalid. If it's valid, then you set IsOpen to false and return. Hiding the popup later again is no problem. But if the input's not valid, the current state of the popup is that IsOpen is true, you skip trying to show it, the method returns, and then the continuation from the cancelled task gets to execute, hiding the popup.

Note that the important part here is that the continuation happens after the method returns. Even if you tried to show the popup unconditionally (i.e. don't check IsOpen before you do that part), the popup would get shown and then immediately hidden.

You could resolve this by using a different timing mechanism (i.e. one where you can extend the timer instead of having to cancel and restart, e.g. System.Threading.Timer), or by using the mechanism you have now but keeping a counter that is incremented each time the invalid state is triggered, decremented each time the timer's await completes, and hide the popup only when the counter reaches zero again.

But really, I think this is all not very good user interaction. As a user, I really have come to hate timed tooltips, because they never stay visible long enough. A much more common technique in WPF is to use one of the built-in validation mechanisms (there are at least three distinct variations on validation in WPF, each with their own pros and cons), and then set the regular tooltip value for the element to your error message, so that the user can see it for as long as they hover the mouse over the element.

There are hurdles and headaches with any sort of user input validation. But if you use one of those standard ones, at least you won't have to deal with this timing issue, and your users might find it works better for them too. :)

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136