Search code examples
c#winforms.net-4.0

Don't raise TextChanged while continuous typing


I have a textbox that has a fairly hefty _TextChanged event handler. Under normal typing condition the performance is okay, but it can noticeably lag when the user performs a long continuous action, such as keeping the backspace button pressed to delete a lot of text at once.

For example, the event took 0.2 seconds to complete, but the user is performing one deletion every 0.1 seconds. Thus, it cannot catch up and there will be a backlog of events that needs to be handled, leading to the UI lagging.

However, the event does not need to run for these in-between states, because it only cares about the end result. Is there any way to let the event handler know that it should process only the latest event, and ignore all the previous stale changes?


Solution

  • I've come across this problem several times, and based on my own experience I found this solution simple and neat so far. It is based on Windows Form but can be converted to WPF easily.

    How it works:

    When TypeAssistant learns that a text change has happened, it runs a timer. After WaitingMilliSeconds the timer raises Idle event. By handling this event, you can do whatever job you wish (such as processing the entered tex). If another text change occurs in the time frame starting from the time that the timer starts and WaitingMilliSeconds later, the timer resets.

    public class TypeAssistant
    {
        public event EventHandler Idled = delegate { };
        public int WaitingMilliSeconds { get; set; }
        System.Threading.Timer waitingTimer;
    
        public TypeAssistant(int waitingMilliSeconds = 600)
        {
            WaitingMilliSeconds = waitingMilliSeconds;
            waitingTimer = new Timer(p =>
            {
                Idled(this, EventArgs.Empty);
            });
        }
        public void TextChanged()
        {
            waitingTimer.Change(WaitingMilliSeconds, System.Threading.Timeout.Infinite);
        }
    }
    

    Usage:

    public partial class Form1 : Form
    {
        TypeAssistant assistant;
        public Form1()
        {
            InitializeComponent();
            assistant = new TypeAssistant();
            assistant.Idled += assistant_Idled;          
        }
    
        void assistant_Idled(object sender, EventArgs e)
        {
            this.Invoke(
            new MethodInvoker(() =>
            {
                // do your job here
            }));
        }
    
        private void yourFastReactingTextBox_TextChanged(object sender, EventArgs e)
        {
            assistant.TextChanged();
        }
    }
    

    Advantages:

    • Simple!
    • Working in WPF and Windows Form
    • Working with .Net Framework 3.5+

    Disadvantages:

    • Runs one more thread
    • Needs Invocation instead of direct manipulation of form