开发者

Progress Bar Only Appears After Work Is Complete?

开发者 https://www.devze.com 2023-01-08 21:11 出处:网络
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

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 sende开发者_StackOverflow社区r, 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
    }
}


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:

  • Create the instance of the BackgroundWorker
  • Set the WorkerSupportsProgress property to true (the default is false)
  • Subscribe to the DoWork event
  • In the DoWork event handler, make calls to the ReportProgress method to report your progress
  • Subscribe to the ProgressChanged event, this is where you will update your progress bar. This is also the event htat is fired when you call the ReportProgrss method mentioned before
  • Subscribe to the RunWorkerCompleted event, this is where you will close your form when done
  • Call the RunWorkerAsync method to start the asynchronous operation

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();
}


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).


 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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消