简体   繁体   中英

Can't update a form while in a loop

I am using visual studio 2008. In the program I am trying to make a button, that when clicked, starts a fairly lengthly loop. It takes a couple minutes to complete. While this is happening, I am trying to send an update of the loops progress to a listbox I have set up. Unfortunately, these updates don't make it to the listbox until the loop is finished. I have tried this with a few different types (progress bar/rich text box etc). I can't seem to get any form changes through while the loop is in progress.

Is there some option or event thing i need to specify to be able to make changes to the form while in a loop?

If you don't need the user to interact (stop or pause) with the process, you can just use:

 lblMyStatus.Text = "Finished " + i.ToString() + "%";
 lblMyStatus.Refresh(); //this forces the label to redraw itself

This will refresh the GUI, but it will still remain unresponsive to user input. If you need to respond to user input while in a loop, then background worker is one of the options...

The lesson here is that you should never do any computationally-intensive work on the GUI thread. That's because the GUI cannot be updated while the processor is busy performing your calculations. Without multiple threads, your application can only do one thing at a time, so something has to be pre-empted. With that in mind, the answer is to spin your long-running computation off onto a separate thread. Using the built-in BackgroundWorker component is the easy solution.

The above-linked documentation even gives a great example:

To try this code, create a Windows Forms application. Add a Label control named resultLabel and add two Button controls named startAsyncButton and cancelAsyncButton. Create Click event handlers for both buttons. From the Components tab of the Toolbox, add a BackgroundWorker component named backgroundWorker1. Create DoWork, ProgressChanged, and RunWorkerCompleted event handlers for the BackgroundWorker. In the code for the form, replace the existing code with the following code:

VB.NET:

Imports System.ComponentModel
Imports System.Windows.Forms

Namespace BackgroundWorkerSimple
    Public Partial Class Form1
        Inherits Form
        Public Sub New()
            InitializeComponent()
            backgroundWorker1.WorkerReportsProgress = True
            backgroundWorker1.WorkerSupportsCancellation = True
        End Sub

        Private Sub startAsyncButton_Click(sender As Object, e As EventArgs)
            If Not backgroundWorker1.IsBusy Then
                ' Start the asynchronous operation.
                backgroundWorker1.RunWorkerAsync()
            End If
        End Sub

        Private Sub cancelAsyncButton_Click(sender As Object, e As EventArgs)
            If backgroundWorker1.WorkerSupportsCancellation Then
                ' Cancel the asynchronous operation.
                backgroundWorker1.CancelAsync()
            End If
        End Sub

        ' This event handler is where the time-consuming work is done.
        Private Sub backgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs)
            Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)

            For i As Integer = 1 To 10
                If worker.CancellationPending Then
                    e.Cancel = True
                    Exit For
                Else
                    ' Perform a time consuming operation and report progress.
                    System.Threading.Thread.Sleep(500)
                    worker.ReportProgress(i * 10)
                End If
            Next
        End Sub

        ' This event handler updates the progress.
        Private Sub backgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
            resultLabel.Text = (e.ProgressPercentage.ToString() & "%")
        End Sub

        ' This event handler deals with the results of the background operation.
        Private Sub backgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            If e.Cancelled Then
                resultLabel.Text = "Canceled!"
            ElseIf e.[Error] IsNot Nothing Then
                resultLabel.Text = "Error: " & e.[Error].Message
            Else
                resultLabel.Text = "Done!"
            End If
        End Sub
    End Class
End Namespace

C#:

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace BackgroundWorkerSimple
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;
        }

        private void startAsyncButton_Click(object sender, EventArgs e)
        {
            if (!backgroundWorker1.IsBusy)
            {
                // Start the asynchronous operation.
                backgroundWorker1.RunWorkerAsync();
            }
        }

        private void cancelAsyncButton_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.WorkerSupportsCancellation)
            {
                // Cancel the asynchronous operation.
                backgroundWorker1.CancelAsync();
            }
        }

        // This event handler is where the time-consuming work is done.
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            for (int i = 1; i <= 10; i++)
            {
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    // Perform a time consuming operation and report progress.
                    System.Threading.Thread.Sleep(500);
                    worker.ReportProgress(i * 10);
                }
            }
        }

        // This event handler updates the progress.
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
        }

        // This event handler deals with the results of the background operation.
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                resultLabel.Text = "Canceled!";
            }
            else if (e.Error != null)
            {
                resultLabel.Text = "Error: " + e.Error.Message;
            }
            else
            {
                resultLabel.Text = "Done!";
            }
        }
    }
}

Application.Doevents is the key to this problem.

This will refresh the listbox and make the program response to other things. For example a button clicks while in the loop. So you can have a button to abort the long running task.

private abort as boolean =false
sub doit()
    abort=false
    do until abort
        listbox1.additem "weee"
        my.application.doevents
    loop
end sub

sub abortbutton_click(...) handles abortbutton.click()
   abort=true
end sub

From MSDN: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents.aspx

If you call DoEvents in your code, your application can handle the other events. For example, if you have a form that adds data to a ListBox and add DoEvents to your code, your form repaints when another window is dragged over it. If you remove DoEvents from your code, your form will not repaint until the click event handler of the button is finished executing.

I would suggest using a background worker to run your loop and implement the UI changes using the ProgressChanged event.

This might get you started http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

You are executing in the UI thread.

While the UI thread is in your loop, it cannot update your form or respond to system messages (such as "Repaint yer winder!").

This is one of the first real issues new forms programmers face. You have two options.

First, if your work is short lived, concede you cannot update your form while your logic is working. Give up. Stop wanting to move that progress bar. Stop it. Just do your work and be done.

The other option is to start multithreading. The other two (at this time) answers suggest this. Threading is hard for new programmers. Hell, its hard for most programmers. I leave it to you to decide if it is worthwhile to attempt it. But, if you do, realize you cannot update the UI from a background thread; you will be Invoke() ing all over the place, believe you me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM