1

I need to retrieve the TextChanged event of the ToolStripTextBox with a delay, to do some stuff after x seconds of stop keypressing..

I found this (and works) example for the TextBox.

https://www.codeproject.com/Articles/20068/Custom-TextBox-that-Delays-the-TextChanged-Event

I tried to convert it for the ToolStripTextBox, but I got this error:

void DelayTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    // stop timer.
    DelayTimer.Enabled = false;

    // set timer elapsed to true, so the OnTextChange knows to fire
    TimerElapsed = true;

    try
    {
        // use invoke to get back on the UI thread.
        this.Invoke(new DelayOverHandler(DelayOver), null);
    }
    catch { }
}

'DelayToolStripTextBox' does not contain a definition for 'Invoke' and no extension method 'Invoke' accepting a first argument of type 'DelayToolStripTextBox' could be found (are you missing a using directive or an assembly reference?)

the ToolStripTextBox doesn't have the 'Invoke` method..

does anybody can help me?

thanks in advance

ghiboz
  • 7,863
  • 21
  • 85
  • 131
  • To change the linked article to `ToolStripTextBox`, you can do either of following options: **1)** Use a `System.WIndows.Forms.Timer` instead of `System.Timers.Timer` which is used in the article and use its `Tick` event. Then you don't need to use Invoke. Just call the method directly. The reason of using `Invoke` in the article is, because `Elapse` event will raise on a different thread than UI thread and you need to use `Invoke` to interact with UI thread. **2)** **Or** use `this.TextBox.Invoke` instead of `this.Invoke`. – Reza Aghaei Feb 01 '18 at 13:35

2 Answers2

2

Why do you use some external code? A timer will do your work easily:

Define a Timer, which will be started when the TextChanges. If the Timer is running, it should be resettet. If Timer.Intervall is reached, we know that we got no new input, because the timmer was not reset. Now the Timer should trigger its Tick-event. The event should fire our method, which we bound to it with t.Tick += ClearText;.

// your trigger
private void textBox1_TextChanged(object sender, EventArgs e)
{
    StartEventAfterXSeconds();
}

private Timer t;

// your time-management
private void StartEventAfterXSeconds(int seconds = 10)
{
    if (t != null)
    {
        t.Stop();
    }
    else
    {
        t = new Timer();
        t.Tick += ClearText;
    }

    t.Interval = 1000 * seconds;
    t.Start();
}

// You action
public void ClearText(object sender, EventArgs args)
{
    this.textBox1.Text = null;
    t.Stop();
}

If you want to use multiple of these TextBoxes you should move everything to a class.

  1. Inherit the Textbox
  2. Add an Event which should be fired after the delay.
  3. Build a method which starts the timer and is fired by the normal TextChanged-event
  4. Implement the Tick-Method which fires your new event and stops the timer.

Some Info about custom-events without the timer with it's tick-method: simple custom event

public class TextBoxWithDelay : TextBox
{
    public TextBoxWithDelay()
    {
        this.DelayInSeconds = 10;

        this.TextChanged += OnTextChangedWaitForDelay;
    }

    public int DelayInSeconds { get; set; }
    public event EventHandler TextChangedWaitForDelay;

    private Timer t;
    private void OnTextChangedWaitForDelay(object sender, EventArgs args)
    {
        if (t != null)
        {
            t.Stop();
        }
        else
        {
            t = new Timer();
            t.Tick += DoIt;
        }

        t.Interval = 1000 * DelayInSeconds;
        t.Start();
    }

    public void DoIt(object sender, EventArgs args)
    {
        if (TextChangedWaitForDelay != null)
        {
            TextChangedWaitForDelay.Invoke(sender, args);
            t.Stop();
        }

    }
}
kara
  • 3,205
  • 4
  • 20
  • 34
  • thanks, but I wish have the code inside my derived class, to be used in different components – ghiboz Feb 01 '18 at 09:17
  • thanks @kara ! it worked!! but I have a question.. when you do `t.Tick += DoIt;` why it works (correctly) once and not each time he's changed? – ghiboz Feb 01 '18 at 09:55
  • 1
    `TextChanged`-Event triggers `OnTextChangedWaitForDelay`. `OnTextChangedWaitForDelay` starts or resets (if running) the timer. when the timer ticks, your method bound to the `TextChangedWaitForDelay`-Event will be invoked. The `DoIt`is bound to the event `Timer.Tick` which is fired every `Interval`. The "magic" is, that the timer is stopped when it fired one tick. – kara Feb 01 '18 at 12:24
  • 1
    Your code is also an external code! So we cannot say *Why do you use some external code?* :) – Reza Aghaei Feb 01 '18 at 13:39
  • Yup - Now with this class it's some sort of extern. The intention of the 1st block was to show the mechanics with a timer. The 2nd block is that result you get, after understanding the 1st one. – kara Feb 01 '18 at 13:48
1

I was also looking for a solution similar to your situation. I know how to do this in Javascript using setTimeout (debounce), so I set out to do something similar in C# via Tasks. Here's what I came up with:

private Dictionary<string, CancellationTokenSource> DebounceActions = new Dictionary<string, CancellationTokenSource>();

private void Debounce(string key, int millisecondsDelay, Action action)
{
    if (DebounceActions.ContainsKey(key))
    {
        DebounceActions[key].Cancel();
    }
    DebounceActions[key] = new CancellationTokenSource();
    var token = DebounceActions[key].Token;
    Task.Delay(millisecondsDelay, token).ContinueWith((task) =>
    {
        if (token.IsCancellationRequested)
        {
            token.ThrowIfCancellationRequested();
        }
        else
        {
            action();
        }
    }, token);
}

Just pass it a unique key to identify which method it's calling from.

alans
  • 1,022
  • 9
  • 17