In order to accomplish this, you are going to have to do the work in a separate thread. The primary reason for that is that WinForms are single-threaded. All UI-related events in a WinForm are handled on the same UI thread. As such, there is no way for the TextChanged
event to fire again while you are still in the middle of processing the previous event. The UI will be locked up until the first event if finished processing.
However, if you do all of the menu-filtering work in another thread, then your UI will be freed-up to react to user input while you are doing the work. Then your TextChanged
event will be allowed to fire before the previous one is done processing.
The easiest way to implement multi-threading in a WinForm project is to use the BackgroundWorker
component. You can find it in the form-designer tool box. Luckily, the BackgroundWorker
component has some properties and methods which are useful for implementing the cancellation as you described.
For instance, here's a very simple example. In this example, every time the text in TextBox
is changed, it starts BackgroundWorker1
performing some work. The work that it does is to simply wait two seconds and then copy the contents of TextBox1
to TextBox2
. If the text changes again before those two seconds are complete, it cancels the bacground work and starts it again from the beginning.
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If BackgroundWorker1.IsBusy Then
BackgroundWorker1.CancelAsync()
Else
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 20
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Exit Sub
End If
Thread.Sleep(100)
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
BackgroundWorker1.RunWorkerAsync()
Else
TextBox2.Text = TextBox1.Text
End If
End Sub
In order for the above example to work, the BackgroundWorker1.WorkerSupportsCancellation
property must be set to True
.
As you can see, when the text changes, it simply checks the IsBusy
property, which determines whether or not the background thread is still working from a previous event. If it is, it cancels it. If it's not, it starts it.
All of the work which needs to be done on the separate thread is done inside the background worker's DoWork
event handler. As it is doing the work, it needs to periodically check whether or not it has been canceled. If it has been canceled, it needs to stop what it's doing and set the Cancel
property of the event args to indicate that it is stopping because it was canceled.
Once the background work is done, (whether by cancellation or by completing its task), the background worker raises the RunWorkerCompleted
event. The event args for that event have a Cancelled
property which indicates whether or not the work completed because it had been canceled prematurely. In the example, if it was canceled, it simply restarts the work from the beginning.
For what it's worth, all of this would be moot if there was some way for you to speed up the menu-filtering algorithm to the point where it's near instantaneous. It may be possible to do that by indexing your menus in something like a suffix array.