The effect you desire to achieve is rather simple with Windows Forms applications. There are many options to get you there, but I will cover the Paint
event option in which we can paint a custom rectangle to display progress to the user. There are two ways to showcase this route effectively; one of which is the simplistic approach using a Timer
and the other is a more in-depth approach but is more appropriate for displaying progress from a background thread.
The Timer
Option
Using a Timer
control we can reproduce this effect with minimal code. You simply need a Panel
, a Timer
and a float
for tracking progress. On FormLoad
we start the timer, on TimerTick
we increment progress and invalidate the panel, and on PanelPaint
we paint our custom progress:
private float progress = 0f;
private void Form1_Load(object sender, EventArgs e) => timer1.Start();
private void timer1_Tick(object sender, EventArgs e) {
progress += 0.01f;
if (progress >= 1.0f)
progress = 0f;
panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.FillRectangle(Brushes.Green, new Rectangle(0, 0, panel1.Width, (int)(panel1.Height * progress)));
}
As you can see, the code for this method is quite trivial and easily explained.
The Event Option
When loading things on a background thread, things can get a little more complicated when updating GUI elements such as your Panel
. In this case I prefer to use a SynchronizationContext
to raise events on the form that will update the GUI elements without raising CrossThreadException
s. In this particular example however, things are relatively simple and work the same way as above, but with more code involved. Say we have a class
that handles all of our background loading and it all happens on a separate thread; in this case we have a custom event that the form subscribes to and the SyncrhonizationContext
raises the event so the form can update the GUI.
public class DataLoader {
#region Fields
private bool loading = true;
private Thread loadingThread;
private SynchronizationContext loadingContext;
#endregion
#region Properties
public float Progress { get; private set; } = 0f;
#endregion
#region Events
public event EventHandler ObjectLoaded;
private void OnObjectLoaded() => loadingContext.Post(new SendOrPostCallback(PostObjectLoaded), new EventArgs());
private void PostObjectLoaded(object data) => ObjectLoaded?.Invoke(this, (EventArgs)data);
#endregion
#region Constructor(s)
public DataLoader() {
if (SynchronizationContext.Current != null)
loadingContext = SynchronizationContext.Current;
else
loadingContext = new SynchronizationContext();
loadingThread = new Thread(new ThreadStart(LoadData));
loadingThread.IsBackground = true;
loadingThread.Start();
}
#endregion
#region Private Methods
private void LoadData() {
while (loading) {
// Do some cool stuff to load data.
CoolStuff();
// Increment progress.
Progress += 0.01f;
if (Progress >= 1.0f)
loading = false;
// Now this object is loaded, raise event for subscribers.
OnObjectLoaded();
}
}
private void CoolStuff() {
// Do cool loading stuff.
}
#endregion
}
Now that our loading class is built and loading objects and all of that fun jazz, we can add it to our form code to do the same thing the timer was doing. This way we subscribe to the ObjectLoaded
event, and follow the same process.
private DataLoader loader;
private void Form1_Load(object sender, EventArgs e) {
loader = new DataLoader();
loader.ObjectLoaded += loader_ObjectLoaded;
}
private void loader_ObjectLoaded(object sender, EventArgs e) => panel1.Invalidate();
private void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.FillRectangle(Brushes.Green, new Rectangle(0, 0, panel1.Width, (int)(panel1.Height * loader.Progress)));
}
Filling a Rectangle from the Bottom
To fill a rectangle from the bottom you’ll have to adjust the position and height simultaneously.
int height = panel1.Height * progress;
Rectangle bounds = new Rectangle(0, panel1.height - height, panel1.Width, height);
e.Graphics.FillRectangle(Brushes.Green, bounds)
You can follow the same concept for right to left and the original example for left to right.
width = panel1.width * progress;
Rectangle bounds = new Rectangle(panel1.Width - width, 0, width, panel1.Height);
There are also techniques for starting at a specific point to achieve some smoke and mirror type looks (such as an odd shaped loader) but I’ll leave these for you to discover if you need them.
Also remember that your Progress
has to be a float
value between 0
and 1
; otherwise you’ll have to divide by 100
when doing the above. In the case where progress is between 0
and 1
just multiply by 100
for display purposes. I always find it easier to keep progress between 0
and 1
since I use it a lot for calculations and only once for display.
Stopping Progress at 100%
From your comment I believe you left the progress increments from my example and are incrementing by 0.01f
. This isn’t the proper way to do this and it was just for the example.
Traditionally you’ll want to figure out your total number of tasks (or in the case of files the total size) and divide the amount completed by that total to get your progress. Below is a basic example with a list of objects to process.
private List<object> ObjectsToLoad = new List<object>();
private void DoCoolStuff() {
int objectsLoaded = 0;
foreach (object o in ObjectsToLoad) {
// Process the object.
Progress = (float)++objectsLoaded / (float)ObjectsToLoad.Count;;
OnObjectLoaded();
}
}
In this particular case, you would remove the call to OnObjectLoaded
in the Load
method’s while loop to prevent duplicate event raising.
If any of the types used are unfamiliar to you, feel free to look at the documentation on MSDN; I've included the most uncommon below, if the one you're interested in isn't listed, I apologize. You can always comment and I'll add it.
Also, to answer a question that may arise with future readers; the reason I used loadingContext.Post
instead of loadingContext.Send
is because in this case I didn't believe the background thread really cared about what the GUI needed to do, it just needed to let the GUI know to do it. Post
tells the thread to continue processing, while Send
tells the thread to wait on the return
from the GUI thread. Send
is best suited when the GUI needs to manipulate the data sent by the background thread and then send it back or update something on the background thread.
Best of luck to you on your future endeavors!