4

I have a function that is being called when the user is typing in a search box. I want to wait for the user to finish typing before I actually execute the function. I know how to easily accomplish this in JavaScript with timeouts, but how would I go about doing the same thing in C#? Also, how long should I wait before I assume the user is done typing? 100ms?

Kirk Ouimet
  • 27,280
  • 43
  • 127
  • 177

6 Answers6

6

If you're comfortable using the Reactive (Rx) framework, then you could implement this functionality using its built in throttling extremely quickly.

Here is an article on the subject: Rx Can Improve UI Responsiveness

And some code stolen & modified from the article:

var textObserver = (from text in Observable.FromEvent<TextChangedEventArgs>(_app.myTextField, "TextChanged")
   select text).Throttle(TimeSpan.FromSeconds(.5));

_searchObserver = textObserver.Subscribe(textChangedEvent =>
   {
      var tb = (TextBox)textChangedEvent.Sender;
      DoMySearch(tb.Text);
   });

As stated in the article (which is worth reading in full), this will fire the code in the lambda expression whenever half a second elapses without the user typing anything.

I'll clean the example up tomorrow when I'm in front of my development PC, but this should give you a starting point now.

Andrew Anderson
  • 3,409
  • 22
  • 25
5

Here's my working code based on Loren's input:

private void SearchTextBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (SearchTextBoxTimer != null)
{
   Console.WriteLine("The user is currently typing.");
   if (SearchTextBoxTimer.Interval < 750)
   {
       SearchTextBoxTimer.Interval += 750;
       Console.WriteLine("Delaying...");
   }
}
else
{
    Console.WriteLine("The user just started typing.");
    SearchTextBoxTimer = new System.Windows.Forms.Timer();
    SearchTextBoxTimer.Tick += new EventHandler(SearchTextBoxTimer_Tick);
    SearchTextBoxTimer.Interval = 500;
    SearchTextBoxTimer.Start();
}
}

And the event handler:

private void SearchTextBoxTimer_Tick(object sender, EventArgs e)
{
    Console.WriteLine("The user finished typing.");
    if (SearchTextBox.Text == "")
    {
        ConsoleTextBox.Text = "Searching: All";
    }
    else
    {
        ConsoleTextBox.Text = "Searching: " + SearchTextBox.Text;
    }
    SearchTextBox_TextChanged();
    SearchTextBoxTimer.Stop();
    SearchTextBoxTimer.Dispose();
    SearchTextBoxTimer = null;
}

If anyone plays with this code, let me know if you tweak the time intervals I put in. I don't think they are optimal quite yet.

Kirk Ouimet
  • 27,280
  • 43
  • 127
  • 177
  • 1
    A bad idea--that Stop should be at the front of the procedure--what happens if a timer interval runs while you're doing the search?? I also think the timer should remain in existence, not be continually created and destroyed. – Loren Pechtel Jun 01 '10 at 23:17
2

Set a timer for the desired delay interval. Start the timer on the key up event. If the timer trips you stop the timer and run your search.

100ms is NOT a good interval, though!! That's about 100wpm even if the typing is absolutely even.

Loren Pechtel
  • 8,945
  • 3
  • 33
  • 45
0

One suggestion is to not surprise the user. This is generally a good UI design principle. So only perform the actual search when the textbox loses focus. It is one thing to show incrementally matching search strings as a user types (like Google), but another to surprise the user with an unsolicited search after some keyboard delay. Personally I would find this irritating, as I often pause to think about a search string while entering it.

Simon Chadwick
  • 1,148
  • 6
  • 12
0

Here is a code sample to solve this

using System;
using System.Timers;
using System.Windows.Input;
using Timer = System.Timers.Timer;

namespace Example
{
    public partial class OnKeyUpInputExample
    {
        // Timer set to elapse after 750ms
        private Timer _timer = new Timer(750) { Enabled = false };

        // Constructor
        public OnKeyUpInputExample()
        {
            //What to do when _timer elapses
            _timer.Elapsed += TextInput_OnKeyUpDone;
        }

        // Event handler
        private void TextInput_OnKeyUp(object sender, KeyEventArgs e)
        {
            // You could also put the .Stop() in an OnKeyDown event handler if you need to account for keys being held down
            _timer.Stop();
            _timer.Start();
        }

        // Function to complement timer elapse
        private void TextInput_OnKeyUpDone(object sender, ElapsedEventArgs e)
        {
            // If we don't stop the timer, it will keep elapsing on repeat.
            _timer.Stop();

            this.Dispatcher.Invoke(() =>
            {
                //Replace with code you want to run.
                Console.WriteLine("KeyUp timer elapsed");
            });
        }
    }
}
gattsbr
  • 3,762
  • 1
  • 22
  • 34
-1

Some views can be found here: Delayed function calls

Record the timestamp of every keyup event, and use the Timer class to delay the execution of a method. Inside that method, you can consider whether the user has stopped typing, comparing the current time to the last keyup event timestamp. You could experiment with the interval, ensuring responsiveness. It's also smart to limit the hits, and only allow searches for a minimum of i.e. 3 characters.

Timer: http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx

Community
  • 1
  • 1
k_b
  • 2,470
  • 18
  • 9
  • The linked solution doesn't work for user input regarding keyup action. Using the above will cause a delay to occur, and then execute, so what will happen is if you type 5 key strokes, it will delay for x amount of time, then execute 5 times. – gattsbr Aug 15 '18 at 17:57