views:

368

answers:

4

I'm using a While loop that loops a certain number of cycles (1-576) based on a value entered by a user. It's activated by the user clicking a "Start" button, But I would like it to be able to be canceled using, preferably, the "Escape" key.

However, when the loop is going I can't get the program to recognize any keypresses.

Private Sub OnGlobalKeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles kh.KeyDown

        lblInput.Text = String.Format("'{0}' Code:{1}", e.KeyCode, CInt(e.KeyCode).ToString())


        If e.KeyCode = CType(27, Keys) Then
                    count = 0
                    loops = 0
        End If
    End Sub

My Loop

Private Sub RUNLOOP()
            While loops >= 1
                  ' my code that runs in the loop
                  loop = loop - 1
            End While
End Sub

While the loop is running my keypresses don't register, otherwise they register fine.

+1  A: 

Inside your loop place the following code:

Application.DoEvents()
Nathan Ridley
This is a bad idea in .NET. You do not need to do it when you have the BackGroundWorker class that is so easy to work with, and this can also lead to reentrancy issues -1
Ed Swangren
I understand that conventional design practices in .Net make this a bad idea, but in the context of this question, it is the correct answer. The alternative is to assist him in a full redesign of his application. You want to volunteer for that?
Nathan Ridley
I didn't know that adding a BackGroundWorker and refactoring a single method constitutes a "full redesign of (an) application". The OP asked how to keep the UI alive and this is a bad (lazy) solution.
Ed Swangren
@Nathan: unfortunately, a complete redesign is the *only* acceptable solution, and hence the only correct answer. `Application.DoEvents` is simply a complete no-go and should never be used. Its inclusion in .NET is arguably a mistake and probably only happened for backwards compatibility reasons (VB6).
Konrad Rudolph
You don't know if the code he is executing within the loop is thread safe (since it's not posted). DoEvents (bad or not) will solve his problem. Obviously background worker is the best option if it fits the the rest of the application design.
badbod99
@badbod99: It may not matter what the code in the loop is doing because it will be still be run on the same thread. Even if he is doing something like modifying controls on a form created on another thread or accessing data structures that are not thread safe there are better ways to do that as well. Honestly, anyone who would sprink DoEvents around their code is just being lazy.
Ed Swangren
@Ed “… is just being lazy.” Or they don't know better, so be careful with accusations. But in any case, it *is* bad code and should not be used.
Konrad Rudolph
@Konrad Rudolph: I suppose that could be the case, but if they have been using .NET for a while and are still using DoEvents( ) I can't think of a good excuse.
Ed Swangren
A: 
While counter <= 10
         ' skip remaining code in loop only if counter = 7
         If counter = 7 Then
            Exit While
         End If

         counter += 1
      End While
Bhaskar
I don't see how this is a solution...
Ed Swangren
Hate the use of Exit While, logic blocks should complete correctly via the logical conditions i.e. when counter> 10. It can make debugging tricky if the exit is buried in code, in the example it's easy to see cos it's only a couple of simple lines but this isn't usually the case in practice.Stefan Rusek is much more elegant solution.
DaveF
+4  A: 

Have you thought of using BackgroundWorker to perform the while loop code in a background thread, making sure your GUI keeps responsive (=resizing/redrawing the form and letting the user "reach" the way you implemented the cancelation of the running work) without needing Application.DoEvents inside the loop to "fake" this behavior?

If you are already on .NET Framework 4.0 you could try and use parallel programming instead of the BackgroundWorker.

peSHIr
Okay, I tried using a BackgroundWorker thread, I believe I have everything set up, but now, it says, "Cross-thread operation not valid: Control 'cmb_x' accessed from a thread other than the thread it was created on." And it seems the solution to THAT is to change almost all my code in the original loop.
But that is the correct thing to do. You should understand how this stuff works.
Ed Swangren
Have you checked out `Control.InvokeRequired`? See http://weblogs.asp.net/justin_rogers/articles/126345.aspx for instance, for some examples.
peSHIr
Okay, I've added a Backgroundworker to my program:But how do I get information from my orignal thread to the new worker thread that I just created? The # of loops is based on what number a user enters into a textbox. I can get information out of the background worker by using .WorkerReportsProgress but how do I get the thread to grab info to begin with?
+2  A: 

There are two ways do to this. You can either use a separate thread for your loop or use Application.DoEvents(). The thread approach will generally give you the best results.

Threaded:

You simply call StopLoop() to tell the loop to stop running. You don't want to set loop = 0 because then you would have to use the Interlocked class to ensure loop was set properly between threads.

Private Sub RunLoop()
  Dim loop As Action = AddressOf InternalRunLoop
  loop.BeginInvoke(null, null)
End Sub

Private bStopLoop As Boolean

Private Sub InternalRunLoop()
  While loops >= 1 And Not bStopLoop
    ' my code that runs in the loop
    loop = loop - 1
  End While
End Sub

Private Sub StopLoop()
  bStopLoop = True
End Sub

DoEvents:

Calling Application.DoEvents() will cause all the pending events on the window's thread to be processed. If each iteration of the loop takes some time, then the app will still appear to the user like it is non-responsive, which is why the threaded approach is preferred.

Private SubRunLoop()
  While loops >= 1 And Not bStopLoop
    ' my code that runs in the loop
    loop = loop - 1
    Application.DoEvents()
  End While
End Sub
Stefan Rusek