views:

2554

answers:

3

Hello,

I'm working in C# and WPF, very new to both.

I have a loop that reads plenty of data from an external source. The process takes about 20 seconds, and I want to show the progress to the user. I don't need any fancy progress bars, so I chose to plot my progress in a label that will say "Step 1/1000", then change to "Step 2/1000" etc.

My code looks something like this:

// "count" is the number of steps in the loop, 
// I receive it in previous code

String countLabel = "/"+count.ToString();

for (i = 0; i < count; i++)
{
    ... do analysis ...
    labelProgress.Content = "Step "+i.ToString()+countLabel
}

However, during that analysis the screen is "stuck" and the progress does not show as advancing. I understand this behavior from my past in C++, where I would probably have a separate thread showing a progress bar receiving notifications from the loop, or some form of repaint/refresh, or forcing the window/app to process its message queue.

What's the right way to do it in C#? I'm not tied to the label, so if there's a simple progress-bar popup screen I could use instead of this label it would also be great...

Thanks

+8  A: 

Move the work to a BackgroundWorker and use the ReportProgress method.

for (i = 0; i < count; i++)
{
    ... do analysis ...
    worker.ReportProgress((100 * i) / count);
}

private void MyWorker_ProgressChanged(object sender,
    ProgressChangedEventArgs e)
{
    taskProgressBar.Value = Math.Min(e.ProgressPercentage, 100);
}
sixlettervariables
Is that thread-safe?
Roee Adler
Yes, it should be. Your background worker is only in one thread, the progress bar update is only on the UI thread. Any thread other than the UI thread will throw an exception if it tries to update the value. So you're guaranteed thread safety with this method.
sixlettervariables
+1  A: 

The UI is not updated due to the fact that your current thread has a higher priority than your UI thread which will ultimately set the label ;). So until your thread has finished to your stuff it will update your label in the end.

Lucky for us, there is a Dispatcher property on every WPF control that lets you fire up a new thread with another priority..

labelProgress.Dispatcher.Invoke(DispatcherPriority.Background,
                    () => labelProgress.Content = string.Format("Step {0}{1}", i, countLabel));

This fire up a thread in the background and would get the job done! You can also try other DispatcherPriority options

HTH

PS I also took the liberty to add an anonymous method and fix your string parsing somewhat.. hope you don't mind..

Arcturus
+2  A: 
    //Create a Delegate to update your status button
    delegate void StringParameterDelegate(string value);
    String countLabel = "/" + count.ToString();
    //When your button is clicked to process the loops, start a thread for process the loops
    public void StartProcessingButtonClick(object sender, EventArgs e)
    {
        Thread queryRunningThread = new Thread(new ThreadStart(ProcessLoop));
        queryRunningThread.Name = "ProcessLoop";
        queryRunningThread.IsBackground = true;
        queryRunningThread.Start();
    }

    private void ProcessLoop()
    {
        for (i = 0; i < count; i++)
        {
            ... do analysis ...
            UpdateProgressLabel("Step "+i.ToString()+countLabel);
        }
    }

    void UpdateProgressLabel(string value)
    {
        if (InvokeRequired)
        {
            // We're not in the UI thread, so we need to call BeginInvoke
            BeginInvoke(new StringParameterDelegate(UpdateProgressLabel), new object[] { value });
            return;
        }
        // Must be on the UI thread if we've got this far
        labelProgress.Content = value;
    }
Dan
BackgroundWorker is provided so you don't have to handle all of the mess of spawning off a thread.
sixlettervariables