20

I'm having some trouble making the progress bar show the updates in real time.

This is my code right now

for (int i = 0; i < 100; i++)
{
     progressbar1.Value = i;
     Thread.Sleep(100);
}

But for some reason the progress bar shows empty when the function runs, and then nothing until the function finishes running. Can someone explain to me how this can be done? I'm new to C#/WPF so I'm not 100% sure on how I would implement a Dispatcher on a different thread (as seen on some other posts) to fix this problem.

To clarify, my program has a button which when press, grabs the value from a textbox, and uses an API to retrieve info, and create labels based on it. I want the progress bar to update after every row of data is finished processing.

This is what I have right now:

private async void search(object sender, RoutedEventArgs e)
{
    var progress = new Progress<int>(value => progressbar1.Value = value);
    await Task.Run(() =>
    {
        this.Dispatcher.Invoke((Action)(() =>
        {
             some pre-processing before the actual for loop occur
             for (int i = 0; i < numberofRows; i++)
             {
                  label creation + adding
                  ((IProgress<int>)progress).Report(i);
             }
        }));
    });
}

Thank you!

nonion
  • 826
  • 2
  • 9
  • 18
  • 1
    http://stackoverflow.com/a/22085142/906773 would be basically what you need. If you have trouble implementing it, could you show more of your actual code? – Jesse Good Apr 26 '15 at 01:44
  • 1
    Get rid of the Dispatcher.Invoke - all you're doing is blocking the UI thread for the whole operation. Progress should handle synchronisation. – Charles Mager Apr 26 '15 at 08:28
  • The problem is that without the Dispatcher.Invoke when the function tries to execute, this is what comes up. "The calling thread cannot access this object because a different thread owns it." This happens when the function tries to access what's inside a textbox. – nonion Apr 26 '15 at 08:30
  • On what line? Are you doing other UI work in there? Add Dispatcher.Invoke only on the parts that are UI related. – Charles Mager Apr 26 '15 at 08:31
  • Yes I'm creating labels, and adding it to the main grid. This also use some external API tools to get information, which goes into the label content. – nonion Apr 26 '15 at 08:32
  • I'd try not to do this. Have the Task process & return some results without touching the UI (other than progress), then update the UI when finished. Better still, look at MVVM for better separating your logic from your UI. – Charles Mager Apr 26 '15 at 08:39

3 Answers3

32

If you are using .NET 4.5 or later, you can use async/await:

var progress = new Progress<int>(value => progressBar.Value = value);
await Task.Run(() =>
{
    for (int i = 0; i < 100; i++)
    {
        ((IProgress<int>)progress).Report(i);
        Thread.Sleep(100);
    }
});

You need to mark your method with async keyword to be able to use await, for example:

private async void Button_Click(object sender, RoutedEventArgs e)
Aleksey Shubin
  • 1,960
  • 2
  • 20
  • 39
  • The problem is that my button click does a loop X times, and I want it so that after each loop, the progress bar updates :\ Can you help me implement this? – nonion Apr 26 '15 at 03:56
  • 1
    @nonion I don't quite understand what you need, but the principle is simple: put all you time-consuming work inside `await Task.Run(() => { }` block (you may have multiple such blocks in a method). And put `((IProgress)progress).Report(i);` whenever you want to update the progress bar. You can put any integer value instead of `i` in the `Report` parameter, and it will be the new value of the progress bar. – Aleksey Shubin Apr 26 '15 at 05:24
  • Oh ok thank you a lot for the help. I tried to compile the code with that implemented and I get this error: The calling thread cannot access this object because a different thread owns it. This is when I try to change the label inside the grid. – nonion Apr 26 '15 at 08:07
  • Worked out how to fix it. (https://stackoverflow.com/questions/9732709/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it) to make the program compiling, but now the problem I'm getting is that the same thing happens, which is that the bar doesn't automatically update – nonion Apr 26 '15 at 08:14
  • @nonion it's hard to say what is wrong without the actual code. Please update your post with your new code. – Aleksey Shubin Apr 26 '15 at 08:18
  • @nonion you didn't *fix* it, you covered up the bug inside `Task.Run`. Aleksey's answer works. – Panagiotis Kanavos Feb 08 '18 at 08:28
26

Managed to make it work. All I needed to do is instead of making it just

progressBar1.value = i;

I just had to do

progressbar1.Dispatcher.Invoke(() => progressbar1.Value = i, DispatcherPriority.Background);
nonion
  • 826
  • 2
  • 9
  • 18
  • 1
    On the contrary, you need to *remove* the call to ` this.Dispatcher.Invoke((Action)(() =>`. By using `Task.Run` you are in a *background* thread and ask to execute the worker function in the *UI* thread. No wonder the progressbar can't update. In this answer you moved the progressbar update to a background thread, thus compounding the error. – Panagiotis Kanavos Feb 08 '18 at 08:25
  • @PanagiotisKanavos Mind sharing your solution? –  Jul 08 '18 at 12:53
  • 1
    @IamDOM in fact, this answer is doubly wrong - not only does it use an obsolete technique, it does so *in place of a good solution*. The question already uses `Progress< T>` so the code already runs in .NET 4.5, – Panagiotis Kanavos Jul 09 '18 at 07:29
5

You should use BackgroundWorker included in .NET, which provides you with methods for reporting the progress of a background thread in an event. The thread which created the BackGroundWorker automatically calls this event.

The BackgroundWorker.ProgressChanged can be used to report the progress of an asynchronous operation to the user.

// This event handler updates the progress bar. 
private void backgroundWorker1_ProgressChanged(object sender,
    ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
}

Refer to MSDN for more information about using this.

Saagar Elias Jacky
  • 2,684
  • 2
  • 14
  • 28