VB.net event handler abort when it is fired again - vb.net

I have an application written in VB.net. In that application I have multiple forms and lots of functionality. The form the application starts with, is some kind of menu. In the background, I have a list of menu items that a user can see and use to open a new form. It is possible to search through all those menu items via a textbox where you can fill in some text and the code then compares all the menu items names to the filled in text and shows the result. This event is fired on every textchanged event of this textbox. But if the user types in a name that occures a lot (like à 100 times or so) the view takes some time (3 to 5 sec) to display all those results. Now I would like to know if it is possible to abort the first event handler if the same event is called again. That means that if I am typing in the textbox and for the first 4 or 5 letters almost all menu items are matches, so I want to abort that search and start a new one right away. Is there any way to detect that the same event is called again and abort the currect one to make the new one start right away?
Thanks in advance for reading this and helping me solve this problem!

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.

Try to add a condition. In your search method, if your text length exceeds some value, call the same event again (like in recursive methods). It should works.
Regards,
Daniel

add some condition on length of search string...like on its Length should be 5 or more
OR maximum results shown at one time should be limited.

Related

MouseEnter, MouseLeave, etc, Event Handlers on a second PictureBox Fail to Fire

I have a large WiseJ vb.net program which has multiple Picturebox controls. An image is loaded into the control on one event, the cursor changes on another etc. Each picturebox use four different events for my functionality. The first picturebox executes the events flawlessly. On the second identically specified picturebox the event handler fails to fire. It won't even execute the breakpoint. I have 8 such picture boxes but only one fires the event handler. I've searched for solutions and tried some but none really address my problem.
I had initially simply copied the working first picturebox code, pasted them, and edited the names to match the next picturebox. This has normally worked well to save time. I then deleted the second picturebox code and went to the page. I double-clicked the offending picture box which generated the click event. The new event still did not fire. I considered a solution which removed the event handler in each picturebox, but frankly, I've added events on pictureboxes so many times and they work fine. I'm guessing something is corrupted behind the scenes but in reviewing the project pages I see no reference to the pictureboxes and am not sure where to look. It's odd that one works fine with all the events but a second fails.
This even works
Private Sub PicFrontLR_Click(sender As Object, e As EventArgs) Handles PicFrontLR.Click
If SpkrDragged = False Then
Cursor = Cursors.Default
LoadSpkrinView(0, "System")
End If
End Sub
This event fails
Private Sub PicFrontC_Click(sender As Object, e As EventArgs) Handles PicFrontC.Click
If SpkrDragged = False Then
Cursor = Cursors.Default
LoadSpkrinView(1, "System")
End If
End Sub
I'm hoping I'm missing something, because one of the solutions involved recreating the page and systematically looking for a failure as I went. This page has a lot of code. Almost done with it and everything else seems to work perfectly.
Well, I figured this one out, mostly. It turns out that a picturebox "apparently" only triggers certain events if there's an image in it. Maybe basic knowledge but I hadn't run into this before. I simply added both a load picture event for the picture box and one for the container. If there's no image the container event fires. If there's an existing picture the picturebox event fires.
One unexplained issue is that the first picture box in a group of eight copied picture boxes, still fires the picturebox event even if there's no picture. As stated the picture box properties are all identical as are the containers. This is what confused me as one event worked and the others did not. Maybe someone can explain.

Why BackgroundWorker infinite loop stops?

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
MsgBox("test")
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
BackgroundWorker1.RunWorkerAsync()
End Sub
Once I click the Button1 ,Messagebox appears.When click okay with "Return" (enter) key it appears again and again...But when I click Return key permanently,almost 5 seconds later programs gives me the run time error.
Error is not English in my computer and I translated it.It is nearly mean "BackgroundWorker is busy,can't do two procces in the same time..."
I know that error.It is because of trying to run BackgroundWorker while it is running.In the code it just start with RunWorkerCompleted event.So it can't start again if "Work is not Completed".Who runs the BackgroundWorker ?
You need to read this post first, it talks about the dangers of re-entrancy and how dialogs (like MsgBox) solve the problem.
Which is what it is not doing in your program. A dialog can only be modal against other windows that are created on the same thread. That worker thread doesn't have any. So there is nothing that stops your Enter key press from also being seen by the control that has the focus. Button1. Note how you can simply use the mouse to select your main window and click the button. Kaboom.
That doesn't go wrong often enough as-is, you can help by leaning on the Enter key so it starts repeating. It will usually be detected by the message box window. But not always, there's a split second between DoWork ending and it starting back up. Just enough to give the UI thread a chance to see the keystroke. Now that Enter keypress operates Button1 and it starts the BGW back up again. So does the RunWorkerCompleted event handler, it cannot be started twice. Kaboom.
That's not the only problem with that dialog, you haven't discovered the other failure mode yet. A dialog needs an owner window, one that it can be displayed on top of. That message box does have one, it has to fallback to the desktop window. Now it is crapshoot which window is going to be in front. Could be the dialog, could be your main window. With the message box underneath your main window. The user cannot see it, has no idea that there is one. No taskbar button either.
Long story short: this cannot work. Only display UI on the UI thread. Only use a worker thread to do non-UI related work.

Why doesn't a MsgBox trigger a lost focus event VB.net?

I have a timer that each time displays a message box saying "Hello." I also have the code configured so whenever the window loses focus, it should stop the timer that keeps the boxes coming. However, they keep coming.
I have tried a similar thing in a similar program with way too long of code to post here, but what it did was it paused the first time, stop the timer, and when the timer was stopped again, it didn't work correctly. There was also some other code there that had a random element, that displayed a different prompt when a certain number was generated, but once it was generated, it kept using that same different prompt every time.
Is this a error of not enough time to process all the code and it "overlaps" some? I can delay the timer without that much different effects, but I think that my [lower end] CPU that it is running this program on, that with 1.6 GHz that it could handle a timer with a few message boxes. Though, VS is running at the same time, but I shouldn't have to export my code and close VS everytime that I need to test it.
If the problem is not enough time, is there a way that I can prevent my program from "multithreading" or whatever it is doing? It seems like a weird problem, but computers are very weird too. :P
Edit:
By "Focus" I mean the selected window that is the most apparent. For example, my browser is now "focused." I have been informed that the correct term is "selected." I must have been using the wrong type of event trigger... :P
It doesn't generate a lost-focus event because the form doesn't have the focus in the first place. A control on the form always gets the focus, like a Button or TextBox. You could use the Deactivate event instead.
Or just not display the message box when the Tick event fires again. Roughly:
Private ShowingMsgBox As Boolean
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
'' Do stuff
''
If Not ShowingMsgBox Then
ShowingMsgBox = True
MsgBox("yada")
ShowingMsgBox = False
End If
End Sub
The underlying reason for this behavior is that MsgBox pumps a message loop. It keeps normal Windows messages getting delivered, like WM_PAINT that keeps the windows painted. And WM_TIMER, the one that generates the Tick event. The only kind of messages that it blocks are input events, mouse and keyboard messages. Otherwise the reason that Application.DoEvents() is so very dangerous. It does the same thing as MsgBox() does, without disabling input.
Create a new project with a Timer (Timer1) and write this code:
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
If (Me.Focused) Then
MessageBox.Show("Hello")
End If
End Sub
If you put the mouse over your form, you would see that a message box will popup after the given Interval is over. If you don't click on the accept button and keep the mouse on the form, you would see that no further messages appear: Me.Focus is False. If you click on the accept button, the messages would start poping up; you don't even need to select the form (the focus is transferred automatically from the MessageBox to the Form).
Summary: the MessageBox does make the Form to lose the focus, although it is a kind of a "tricky" lost as far as will automatically come back after clicking on the accept button.
UPDATE: the proposed configuration does trigger a LostFocus event of the form:
Private Sub Form1_LostFocus(sender As Object, e As System.EventArgs) Handles Me.LostFocus
MsgBox("lost")
End Sub
Unlikely the other answers/comments, what I understood from your question is that you want to know the reason and if this is a normal behaviour, rather than getting a working solution to make the form to lose the focus (you are not even describing the exact conditions under which you want this to happen).

Backgroundworker - UI still not responsive until BGW completed

I implemented a BGW to my application in hopes of speeding up loading time for the starting window(40+ controls)
I will not post my whole code as it's far too long but will give you the gist of the code. I split big function calls that take time to complete alongwith a handful of controls and moved them into the BGW in hopes of asyncronously loading controls to help quicken the process.
It is understood that I have to move UI changing code to the ProgressChanged event or RunWorkerCompleted event, which I have done. I originally had all code thrown into the DoWork event and it was extremely fast but found out it's not safe so I had reworked it to move all UI-related oode to the ProgressChanged. It's not nearly as fast now and it seems that the BGW controls wait until the UI thread completes prior to changing the controls in the BGW_ProgressCHanged event. I never saw this 'lag' between the two when I had all the changes in DoWork. What can I do about this? Or can I at least had the BGW update the controls realtime rather than waiting for all controls are completed before updating all controls?
The responsiveness is lower as well as it locks up the window to wait for the BGW controls to update. Just looking for what could possibly be happening
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SyncLock <globalVar>
BackgroundWorker1.ReportProgress(0, "Tom")
End SyncLock
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim value As String = DirectCast(e.UserState, String)
Select Case e.ProgressPercentage
Case 0
lblName.text = value
lblName.Visible = true
End Select
End Sub
You removed all evidence of the problem in your code, but the diagnostic is an excellent match. You have a fire hose problem. Your code is calling ReportProgress far too often.
Things go wrong when your ProgressChanged event handler needs more time than the time between ReportProgress calls. Which is like drinking from a fire hose, no matter how fast you swallow the water, you just can't keep up with the flow.
Which is what the UI thread experiences. When it finishes the call to your ProgressChanged event handler, there's yet more water, yet another request to call the handler. That relentlessly continues without the UI thread ever being able to keep up. It now doesn't get around to doing its normal duties anymore. Which means that your UI stops updating, paints are no longer performed. And it doesn't respond to input anymore.
This can last for a while after the worker thread stopped running, the UI is still trying to work down the backlog of requests. When it finally gets there, the UI suddenly springs back to life.
A simple way to diagnose this condition is to add this method call after the ReportProgress call:
System.Threading.Thread.Sleep(45)
Which slows down the worker thread enough to limit the number of ReportProgress() calls to no more than 21 per second. Which is plenty fast enough for human eyes, anything faster just looks like a blur so is wasted effort. If that fixes the problem then you found the cause.
Using Sleep() like this is otherwise an ugly Q&D solution for the problem, it of course also slows down your worker so its gets less work done. You'll have to improve your code so that this doesn't happen and just makes less ReportProgress calls.
One thing you might want to add before starting the worker:
Me.SuspendLayout()
Then, in the RunWorkerCompleted event:
Me.ResumeLayout()
That should suspend all layout logic until all the work is done, then update the entire form in 1 operation.
EDIT
Replace
BackgroundWorker1.ReportProgress(...)
With
DirectCast(sender, BackgroundWorker).ReportProgress(...)
And get rid of the synclock.
EDIT2
The correct way to feed the data to the DoWork event is through the DoWorkEventArgs.
Start work like this:
BackgroundWorker1.RunWorkerAsync(<globalvar>)
And access it like this:
Dim globalVarData As Object = e.Argument

vb.net activated fires multiple times

I want to update a database based on the form that is currently activated. I had originally decided to use the GotFocus event. However I now understand that will not work as the form has controls on it. So I then thought I wouls use the activated event. This works but seems to fire multiple times. I put in the following code:
Private Sub frmNewTicket_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Activated
MsgBox("Form Activated")
End Sub
I select the form and make it activated and the message box appears about 15 times.
Why does it do this? How should I handle this. I only want my code to execute once when the form is activated.
NOTE: There are several forms that the users will be changing between, incuding forms from other applications.
Each time you click OK on the messagebox, the form regains the focus and is activated again.
Put a static Boolean value in your frmNewTicket_Activated like someone has posted here:
Static HasRan As Boolean=False
If Not HasRan Then
HasRan=True
'put code here
End If
It sounds like you are wanting to do something everytime your form gets activated. The Form Activated event will work fine as long as what you are doing doesn't pull focus from the Form which will then trigger another Activation event when the Form gets focus again. Try using something other than a MessageBox for testing like Beep or changing the Form's Color