0

I have a panel on winform itch with a button, 2 labels, and an image. Now I would like to change the color of the panel background gradually from bottom to top as a progress bar. I tried with a panel with the components with the second panel on top of the first but behind the components and then gradually expand the height of the second panel. But the component's background color reminds the color of the first panel. I have tried to set the component's background color to transparent.

Do anyone have a suggestion to achieve this effect? It doesn't have to be with panels all I want is that the components stay on top of the area and that the background color change.

Thanks in advance

Anders Pedersen
  • 1,034
  • 1
  • 9
  • 20
  • 1
    You can always use the `OnPaint` event to custom paint a rectangle from bottom to top over time (perhaps firing off some kind of timer to incrementally raise the paint event or perform the paint altogether). I believe you can use `panel1.Invalidate()` to force the control to repaint itself which in turn raises the `OnPaint` event for you to repaint the progress. Writing up an answer now. – Hazel へいぜる Nov 28 '18 at 15:49

1 Answers1

2

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 CrossThreadExceptions. 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!

Hazel へいぜる
  • 2,751
  • 1
  • 12
  • 44
  • Thank you very much. This was what I was looking for. I have test application running with booth examples :-) A couple of questions: Maybe a stupid question but how can I make fill up the panel from the bottom? I'm not sure what you exactly do with Hex values and the increment of Progress. And in the second example how can I ensure that panel stops filling at 100% when the "Do cool stuff" is finished? Let say that cool stuff is copying som big files? – Anders Pedersen Nov 28 '18 at 22:27
  • 1
    @AndersPedersen to fill the panel from the bottom you have to adjust the rectangle position and height; I’ll update my post with an example. Not sure what you mean by hex values. As far as stopping progress at 100% you’ll have to divide your tasks by what’s been accomplished; I’ll update with a sample on that too. If you’re loading a file there’s usually a way to tell how much you’ve processed but the exact implementation will vary depending on how you’re loading it. Give me a bit to post the samples. I’ll comment when done. – Hazel へいぜる Nov 29 '18 at 01:18
  • 1
    @AndersPedersen I updated my post, if anything seems off or doesn’t work let me know and I’ll fix it tomorrow morning. I’m on my phone right now as I don’t have my computer with me at the moment. Also, if you clarify your question on the hex values I can try to answer that too. If the post has solved your issue remember to mark it as answered. :) – Hazel へいぜる Nov 29 '18 at 02:05
  • Thanks again for the outstanding support. I have it working filling it from the bottom with the List example. The only thing missing was after the ForEach - loading = false; To stop the while loop. – Anders Pedersen Nov 29 '18 at 09:03
  • Just anothe question. Where dos this SynchronizationContext relate to Async/Await way of programming? – Anders Pedersen Nov 29 '18 at 09:06
  • Now I understand the Hex - it's just a way to incremental raise the Progress property and keeping it between 0 and 1. Right? – Anders Pedersen Nov 29 '18 at 09:08
  • 1
    @AndersPedersen I’m still not quite sure what you mean by hex; if you’re talking about the 0.01f then that’s a float value, don’t lest the f fool you. As far as the synchronization context goes, it’s kind of like a messenger that helps the background thread communicate with the GUI thread. The closest it gets to async await is the send vs post which I cover above. If your background thread is running asynchronous to your GUI thread and you use Send then it’s similar to an await while using Post the background thread sends the message and doesn’t wait around. – Hazel へいぜる Nov 29 '18 at 11:44