I want to display a progress bar while doing some work, but that would hang the UI and the progress bar won't update.
I have a WinForm ProgressForm with a ProgressBar
that will continue indefinitely in a marquee fashion.
using(ProgressForm p = new ProgressForm(this))
{
//Do Some Work
}
Now there are many ways to solve the issue, like using BeginInvoke
, wait for the task to complete and call EndInvoke
. Or using the BackgroundWorker
or Threads
.
I am having some issues with the EndInvoke, though that's not the question. The question is which is the best and the simplest way you use to handle such situations, where you have to show the user that the program is working and not unresponsive, and how do you handle that with simplest code possible that is efficient and won't leak, and can update the GUI.
Like BackgroundWorker
needs to have multiple functions, declare member variables, etc. Also you need to then hold a reference to the ProgressBar Form and dispose of it.
Edit: BackgroundWorker
is not the answer because it may be that I don't get the progress notification, which means there would be no call to ProgressChanged
as the DoWork
is a single call to an external function, but I need to keep call the Application.DoEvents();
for the progress bar to keep rotating.
The bounty is for the best code solution for this problem. I just need to call Application.DoEvents()
so that the Marque progress bar will work, while the worker function works in the Main thread, and it doesn't return any progress notification. I never needed .NET magic code to report progress automatically, I just needed a better solution than :
Action<String, String> exec = DoSomethingLongAndNotReturnAnyNotification;
IAsyncResult result = exec.BeginInvoke(path, parameters, null, null);
while (!result.IsCompleted)
{
Application.DoEvents();
}
exec.EndInvoke(result);
that keeps the progress bar alive (means not freezing but refreshes the marque)
I have to throw the simplest answer out there. You could always just implement the progress bar and have no relationship to anything of actual progress. Just start filling the bar say 1% a second, or 10% a second whatever seems similar to your action and if it fills over to start again.
This will atleast give the user the appearance of processing and make them understand to wait instead of just clicking a button and seeing nothing happen then clicking it more.
If you want a "rotating" progress bar, why not set the progress bar style to "Marquee" and using a
BackgroundWorker
to keep the UI responsive? You won't achieve a rotating progress bar easier than using the "Marquee" - style...We are use modal form with
BackgroundWorker
for such a thing.Here is quick solution:
And how we use it:
What on earth does the fact that you're not getting progress notification have to do with the use of
BackgroundWorker
? If your long-running task doesn't have a reliable mechanism for reporting its progress, there's no way to reliably report its progress.The simplest possible way to report progress of a long-running method is to run the method on the UI thread and have it report progress by updating the progress bar and then calling
Application.DoEvents()
. This will, technically, work. But the UI will be unresponsive between calls toApplication.DoEvents()
. This is the quick and dirty solution, and as Steve McConnell observes, the problem with quick and dirty solutions is that the bitterness of the dirty remains long after the sweetness of the quick is forgotten.The next simplest way, as alluded to by another poster, is to implement a modal form that uses a
BackgroundWorker
to execute the long-running method. This provides a generally better user experience, and it frees you from having to solve the potentially complicated problem of what parts of your UI to leave functional while the long-running task is executing - while the modal form is open, none of the rest of your UI will respond to user actions. This is the quick and clean solution.But it's still pretty user-hostile. It still locks up the UI while the long-running task is executing; it just does it in a pretty way. To make a user-friendly solution, you need to execute the task on another thread. The easiest way to do that is with a
BackgroundWorker
.This approach opens the door to a lot of problems. It won't "leak," whatever that is supposed to mean. But whatever the long-running method is doing, it now has to do it in complete isolation from the pieces of the UI that remain enabled while it's running. And by complete, I mean complete. If the user can click anywhere with a mouse and cause some update to be made to some object that your long-running method ever looks at, you'll have problems. Any object that your long-running method uses which can raise an event is a potential road to misery.
It's that, and not getting
BackgroundWorker
to work properly, that's going to be the source of all of the pain.Indeed you are on the right track. You should use another thread, and you have identified the best ways to do that. The rest is just updating the progress bar. In case you don't want to use BackgroundWorker like others have suggested, there is one trick to keep in mind. The trick is that you cannot update the progress bar from the worker thread because UI can be only manipulated from the UI thread. So you use the Invoke method. It goes something like this (fix the syntax errors yourself, I'm just writing a quick example):
The
InvokeRequired
property will returntrue
on every thread except the one that owns the form. TheInvoke
method will call the method on the UI thread, and will block until it completes. If you don't want to block, you can callBeginInvoke
instead.For me the easiest way is definitely to use a
BackgroundWorker
, which is specifically designed for this kind of task. TheProgressChanged
event is perfectly fitted to update a progress bar, without worrying about cross-thread calls