Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET --- Textbox control - wait till user is done typing

Greetings all,

Is there a built in way to know when a user is done typing into a textbox? (Before hitting tab, Or moving the mouse) I have a database query that occurs on the textchanged event and everything works perfectly. However, I noticed that there is a bit of lag of course because if a user is quickly typing into the textbox the program is busy doing a query for each character. So what I was hoping for was a way to see if the user has finished typing. So if they type "a" and stop then an event fires. However, if they type "all the way" the event fires after the y keyup.

I have some ideas floating around my head but I'm sure they aren't the most efficient. Like measuring the time since the last textchange event and if it was > than a certain value then it would proceed to run the rest of my procedures.

let me know what you think.

Language: VB.NET Framework: .Net 2.0

--Edited to clarify "done typing"

like image 524
Cj Anderson Avatar asked Mar 22 '09 22:03

Cj Anderson


3 Answers

One approach:

  1. Create a Timer with an Interval of X milliseconds

    The interval should be about 300ms; more than a normal time between keystrokes, and also reasonable time to wait between finishing and the update occurring

  2. In the input's TextChanged event, Stop() and then Start() the Timer

    This will restart the Timer if it is already running, so if the user keeps typing at a normal rate, each change will restart the timer.

  3. In the timer's Tick event, Stop() the Timer and do the long transaction

  4. Optional: Handle the Leave and KeyDown events so that leaving the control or pressing Enter will Stop() the Timer and do the long transaction.

This will cause an update if the text has changed, and the user hasn't made any changes in X milliseconds.

One problem with the "Measure the time since the last update" approach you're considering is that if the last change is made quickly, the update won't happen, and there won't be any subsequent changes to trigger another check.

Note: There must be a one to one pairing between TextBoxes and Timers; if you're planning on doing this with more than one input, I'd consider building a UserControl that wraps this functionality.

like image 162
Daniel LeCheminant Avatar answered Oct 13 '22 05:10

Daniel LeCheminant


For those who need something like this in .NET 2.0, here I made a Control that derives from TextBox and uses the same approach.. Hope this help

public partial class TextBox : System.Windows.Forms.TextBox
{

    private ManualResetEvent _delayMSE;
    public event EventHandler OnUserStopTyping;
    private delegate bool TestTimeout();

    public TextBox()
    {
        _delayMSE = new ManualResetEvent(false);
        this.TextChanged += new EventHandler(TextBox_TextChanged);
    }

    void TextBox_TextChanged(object sender, EventArgs e)
    {


        _delayMSE.Set();
        Thread.Sleep(20);
        _delayMSE.Reset();

        TestTimeout tester = new TestTimeout(TBDelay);
        tester.BeginInvoke(new AsyncCallback(Test), tester);

    }


    private void Test(IAsyncResult pResult)
    { 
        bool timedOut = (bool)((TestTimeout)pResult.AsyncState).EndInvoke(pResult);
        if (timedOut)
        {
            if (OnUserStopTyping != null)
                OnUserStopTyping(this, null);
        }
    }

    private bool TBDelay()
    { 
        return !_delayMSE.WaitOne(500, false); 
    }

}
like image 42
Diego Carballo Avatar answered Oct 13 '22 05:10

Diego Carballo


I ended up trying Scott Weinstein answer although it required some deeper knowledge about threading, delegates and basic lambda syntax. And it worked pretty well. His orginal answer wasn't pure copy-paste so I had to do some playing around to get it work.

I added very little time to Thread.Sleep, since I noticed invoke method can happen twice if user is typing really quickly but with tiny random delay between some of keystrokes. You have to also add reference to WindowsBase assembly for using Dispatcher.

I use 1,5 seconds to wait user end typing.

    // use manual reset event to Q up waiting threads.
    // each new text changed event clears the Q
    // only the last changed will hit the timeout, triggering the action
    private ManualResetEvent _delayMSE;
    private Func<bool> TBDelay;
    private delegate void ActionToRunWhenUserStopstyping();

    public Form1()
    {
        InitializeComponent();

        _delayMSE = new ManualResetEvent(false);
        TBDelay = () => !_delayMSE.WaitOne(1500, false);
    }

    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        _delayMSE.Set(); 

        // open the ResetEvent gate, to discard these delays    
        Thread.Sleep(20);
        // let all pending through the gate    
        _delayMSE.Reset();
        // close the gate
        TBDelay.BeginInvoke(res =>    
        {        
            // callback code        
            // check how we exited, via timeout or signal.        
            bool timedOut = TBDelay.EndInvoke(res);
            if (timedOut)
                Dispatcher.CurrentDispatcher.Invoke(
                    new ActionToRunWhenUserStopstyping(DoWhatEverYouNeed), 
                    DispatcherPriority.Input);
        }, null);
    }

    private void DoWhatEverYouNeed()
    {
        MessageBox.Show(textBox1.Text);
    }
like image 3
Clack Avatar answered Oct 13 '22 05:10

Clack