You have discovered the primary problem with single-threaded applications... long running operations freeze the user interface.
Your DoEvents()
call essentially "pauses" your code and gives other operations, like the UI, a chance to run, then resumes. The problem is that your UI is now frozen again until you call DoEvents()
again. Actually, DoEvents()
is a very problematic approach (some call it evil). You really should not use it.
You have better options.
Putting your long running operation in another thread helps to ensure that the UI remains responsive and that your work is done as efficiently as possible. The processor is able to switch back and forth between the two threads to give the illusion of simultaneous execution without the difficulty of full-blown multi-processes.
One of the easier ways to accomplish this is to use a BackgroundWorker, though they have generally fallen out of favor (for reasons I'm not going to get into in this post: further reading). They are still part of .NET however and have a lower learning curve then other approaches, so I'd still suggest that new developers play around with them in hobby projects.
The best approach currently is .NET's Tasks library. If your long running operation is already in a thread (for example, it's a database query and you are just waiting for it to complete), and if the library supports it, then you could take advantage of Tasks using the async keyword and not have to think twice about it. Even if it's not already in a thread or in a supported library, you could still spin up a new Task and have it executed in a separate Thread via Task.Run(). .NET Tasks have the advantage of baked in language support and a lot more, like coordinating multiple Tasks and chaining Tasks together.