views:

91

answers:

5

I'm trying to make a progress bar show up on a form, but for some reason the form isn't actually visible until the process is over, and it is closed when the process is over (or in other words, the form is only open for an instant). How can I make it so the form shows up at the beginning of the process?

Note: my code might not be 100% correct, I'm just trying to make it different than my own for confidentiality reasons.

public void SpawnPizzaProgressBarForm(object sender, EventArgs e)
{
    FormPizzaProgressBar Form = new FormPizzaProgressBar();
    Form.ShowDialog();
}
...
public void ProgressBarForm_Load(object sender, EventArgs e)
{
    Pizza = new Pizza();
    Pizza.Eat(PizzaEatingProgressBar);
    this.Close();
}
...
public void Eat(ProgressBar PizzaEatingProgressBar)
{
    foreach(var Slice in Pizza)
    {
        Slice.Clear(); //
        PizzaEatingProgressBar.Value = (Slice.Index / Pizza.Count())*100
    }
}
+1  A: 

This looks like a tight loop - you need to either place the work onto a background thread using the BackgroundWorker, or cheat and give the UI time to update by calling Application.DoEvents() (processes the message queue, which includes paint commands).

If the loop is too tight or the work too heavy, the UI will largely block until it completes, even with UI changes inter-mingled.

Adam
A: 

You need to use threading to get the UI to update while a BackgroundWorker handles your actual process. Check out this article (from the esteemable Jon Skeet) before you get started.

AllenG
+6  A: 

This happens because you do all the processing in the Load event for the form. This is called before the form is shown for the first time.

While in the event handler, you are preventing the form from actually showing, as the event handler has to complete before anything else can be processed.

What you want to do is use a BackgroundWorker instance to perform your work. This requires you to do the following:

You have some issues here in that you have your Pizza class tightly coupled to the progress bar. This isn't a good idea. Rather, you should have an event that is fired to indicate that the progress has changed, and then call the ProgressChanged event from the event handler for your Pizza instance.

I've moved out the code for your Eat method and encapsulated it in the form to show you an example of how to use the BackgroundWorker class, but the ideal solution would be to expose an event to indicate when the amount of pizza consumed changes.

Also note that you should override the protected Dispose method on the Form class to properly dispose of the BackgroundWorker instance when the form is disposed of.

Here is what the example looks like:

public void SpawnPizzaProgressBarForm(object sender, EventArgs e)
{
    FormPizzaProgressBar Form = new FormPizzaProgressBar();
    Form.ShowDialog();
}

...

BackgroundWorker worker = new BackgroundWorker();

public void ProgressBarForm_Load(object sender, EventArgs e)
{
    // Initialize the background worker.
    worker = new BackgroundWorker();

    // Indicate that the worker supports progress.
    worker.WorkerSupportsProgress = true;

    // Subscribe to the DoWork event.
    worker.DoWork += (s, e) => {
        // Create the pizza instance.
        Pizza = new Pizza();

        // Process the slices.
        foreach (var Slice in Pizza)
        {
            // Clear the slice.
            Slice.Clear();

            // Report the progress.
            worker.ReportProgress(Slice.Index / Pizza.Count() * 100);
        }
    };

    // Subscribe to the ProgressChanged event.
    worker.ProgressChanged = (s, e) => {
        // Update the progress bar.
        PizzaEatingProgressBar.Value = e.ProgressPercentage;
    };

    // Subscribe to the RunWorkerCompleted event.
    worker.RunWorkerCompleted = (s, e) => {
        // Close the dislog.
        this.Close();
    };
}

// Must override to properly dispose of the background worker.
protected override void Dispose(bool disposing)
{
    // Call the base.
    base.Disposing(disposing);

    // Dispose of the background worker if disposing is true.
    if (disposing) worker.Dispose();
}
casperOne
+1 for spotting the event, I didn't pay attention to that and just assumed painting issues with UI thread blocking.
Adam
+2  A: 

Winforms is based on the Windows API, which doesn't update the GUI elements unless the thread on which the GUI elements were created is "pumped". WinForms called your ProgressForm_Load method on the thread on which it must "pump" messages in order for GUI elements to update. You aren't pumping the message queue during your operation in Eat. So, if the GUI elements don't update, you won't see the progress bar change.

the simplest solution is to call Application.DoEvents periodically in your Eat method. The better answer is to do the long operation on a different thread, display an hourglass during that operation, and periodically inform the progress bar to update by using the Invoke style of GUI updating (you can't call most GUI elements' methods directly from a thread other than the thread on which they were created).

David Gladfelter
+1  A: 
 PizzaEatingProgressBar.Value = (Slice.Index / Pizza.Count())*100

That doesn't work if Slice.Index as an integer. You need to cast it to double to get a floating point division. For example:

 PizzaEatingProgressBar.Value = (int)((double)Slice.Index / Pizza.Count) * 100);

or do it like this:

 PizzaEatingProgressBar.Value = (Slice.Index * 100) / Pizza.Count;

Which ensures the division cannot truncate to 0 like your original expression did. Contrary to everybody's advice, a ProgressBar does paint itself when you change the Value property. Although your window still goes catatonic.

Hans Passant