I am creating a 'cleaner' application using windows forms c# and want to have a progress-bar. At the moment I am just cleaning temp files, cache files and browser junk.
I have a progress bar:
private System.Windows.Forms.ProgressBar _myProgressBar;
And here is my cleanup method:
private void button1_Click_1(object sender, EventArgs e)
{
_myProgressBar.Value = 0;
ClearCache();
_myProgressBar.Value = 25;
ClearCookie();
_myProgressBar.Value = 50;
ClearHistory();
_myProgressBar.Value = 75;
TempCleaner();
_myProgressBar.Value = 100;
}
I want it to execute those 4 methods (each of which takes a few seconds to complete) while updating the progress bar as it goes; but all it does is wait until they have finished and then show the progress bar as completed. How can I make the progress bar show the progress as it completes each part?
This is a complicated subject, and the code sample and your reputation indicates that you may be a little inexperienced; which is a tricky combination. But I'm going to try to answer... (other readers please bear in mind I'm going to try to keep this answer simplistic).
The reason it's complicated is that in order to do it properly, you're going to need to use threading. Threading is a massive and tricky topic, which causes problems even for experienced developers. I cannot possibly hope to reasonably cover that topic all here, so I'm just going to stick to the key points.
When your WinForms application is running, unless you arrange it specially, your code will be running on what can be called the "user interface thread" ("UI thread"). Ideally, that thread should be left to do things like processing events on the window (like moving, resizing, mouse clicks, etc). If you want to do anything which is going to take time (like the contents of your "Click" method above), then you need to execute that functionality on a background thread (not directly within the method, as you've done above)
Something you need to know is that although the work is going to be done on a background thread, the changes to the progress bar will have to be done by the UI thread - this is mandatory, you are not allowed to do it from a background thread.
So here are the key points to my proposed solution:
- Move the code from the Click method into a background worker, and add the notification of progress from that worker as it progresses
- Update the progress bar from the correct thread
So here we go...
1) There are lots of ways of doing this. Here's one... move all your code from the Click event into a method called "BackgroundThread", and modify your click method so you have this...
private void button1_Click_1(object sender, EventArgs e)
{
if (_thread == null)
{
_thread = new Thread(new ThreadStart(BackgroundThread));
_thread.IsBackground = true;
_thread.Start();
}
}
private void BackgroundThread()
{
UpdateProgress(0);
ClearCache();
UpdateProgress(25);
ClearCookie();
UpdateProgress(50);
ClearHistory();
UpdateProgress(75);
TempCleaner();
UpdateProgress(100);
}
You'll need to declare that "_thread" variable as a private member variable on your Form (to prevent it being garbage collected).
Now I'm going to highlight but not solve a couple of problems that you will need to deal with. Since we have moved the code onto a background thread, there will be nothing stopping the user pressing the button lots of times, each of which would fire off a new thread (probably not what you want). How you deal with that problem depends on how the progress bar sits in your UI, and how often you want people to press the button. A simple (but incomplete) answer would be to wrap the contents of the Click method in "if(_thread==null)" (as shown above), meaning that the button would only work the first time you clicked it.
The other problem is notifying your background thread if you wanted it to stop what it was doing (e.g. user wants to close the application). As I've said, it's a big topic, and I'm not going to get into that here.
3) The above code needs us to write the UpdateProgress method...
private void UpdateProgress(int percent)
{
RunOnUiThread(() => _myProgressBar.Value = percent);
}
private void RunOnUiThread(Action action)
{
if (InvokeRequired)
{
Invoke(action);
}
else
{
action();
}
}
You will have guessed the "_myProgressBar.Value = percent"; but the rest of it probably looks bizarre. The RunOnUiThread method is reusable on any WinForm Form or Control, so you might like to keep that in a snippet. Basically, whatever action you give it, it will check whether it's on not the UI thread ("InvokeRequired"). We're passing it a lambda expression "() =>" to do what we need to happen on the UI thread.
If it's on the wrong thread, it will "Invoke" the action (basically queue it up so that the UI thread will do it when it gets the chance). If it was already on the right thread then it will just execute the action on the same thread.
That should do it.