0
votes

I would like to make a dedicated class to update the progress bar in my apps (in this case a WPF progressbar). I did something like this :

public class ProgressBarUpdate : IDisposable
{
    private readonly double _delta;
    private int _current;
    private int _total;
    private readonly ProgressBar _pb;

    public ProgressBarUpdate(ProgressBar pb, int total)
    {
        _pb = pb;
        _total = total;
        // the pb.Maximum is a double so it doesn`t get truncated
        _delta = _pb.Maximum / total; 
        _current = 0;
        _pb.Visibility = Visibility.Visible;

    }
    public void Dispose()
    {
        _pb.Visibility = Visibility.Collapsed;
        _current = 0;
    }
    public void UpdateProgress()
    {
        _pb.Value =(int)_delta * (++_current);
    }

That i use like this (in the UI thread) :

using (var pu = new ProgressBarUpdate(pb, totalCount)
{
  for (x=0; x<totalCount; x++)
  {
     // operations here 
     pu.UpdateProgress()
  }
}

But the UI, probably blocked, is not updating correctly. What is the best way to display all the progress?

4
Might it be, that your UI is not only "not updating" but also blocking?TGlatzer
have you debugged into it to make sure _pb.Value is getting changed as you expect inside the UpdateProgress call?Tim
But this is not updating correctly in my UI. What does that mean?David Heffernan
You are probably doing your using(var pu ... blah on the UI thread and blocking any drawing from happening until the work is completeCharleh
Stop blocking the UI thread. Put the long running task on another thread.David Heffernan

4 Answers

1
votes

Winforms/WPF program is an Eventing system. There is a single thread which continuously processes events from an event queue. That is its main job and ideally that is the only thing which it should do. Any sort of UI activity generates events in the event queue - like you move your mouse over the window or click something or some other window overlaps your window and then again when it goes away from the overlapped position. All these events are processed by the UI thread and that keeps the UI updated all the time.

Further, Winforms/WPF make it necessary to access and/or update controls and their properties in a thread safe manner by allowing it only on the UI thread.

If you block this UI thread or do some other CPU bound calculation on it, then your UI responsiveness and updated behavior will suffer. Worst case UI will freeze.

Hence the correct answer for you is to do your calculation loop on another worker thread and only update the progress bar UI by marshaling the call to UI thread using the Dispatcher.

However, to answer your question and satisfy your inquisition, here is something that is possible - but it is bad practice and your should never do the following...:

To make it simple, when you update the Value property of the progress bar, it invalidates the progress bar UI - so, UI must update. Hence lets say an event is generated in the event queue which will cause some code to run which will update the UI. However, you are running in a loop over the UI thread - so, the thread has no chance to process this event unless your loop is over. Hence you don't see any UI update. The trick is to make the UI thread process that event before you make the next update on the Value of progress bar. You can do this by forcefully invoking a lower priority item into the event queue - so that normal and higher priority items are processed before going to the next iteration.

using (var pu = new ProgressBarUpdate(pb, totalCount))
{
  for (int x = 0; x < totalCount ; x++)
  {
     // operations here 
     pu.UpdateProgress();

     Dispatcher.Invoke(DispatcherPriority.Background, new Action(()=>{}));
  }
}
0
votes

If you're doing your work, and calling UpdateProgress, on the UI thread then it won't update until you finish the work and the UI thread can do other work (like refresh the UI). So this will never work.

If you're doing your work on a background thread, then you need to use a Dispatcher to marshal the setting the value to the UI thread.

Here's an example from http://tech.pro/tutorial/800/working-with-the-wpf-dispatcher

if (!myCheckBox.Dispatcher.CheckAccess())
{
  myCheckBox.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    new Action(
      delegate()
      {
        myCheckBox.IsChecked = true;
      }
  ));
}
else
{
  myCheckBox.IsChecked = true;
}
0
votes

Try this:

public ProgressBarUpdate(ProgressBar pb, int total)
{
    _pb = pb;
    _total = total;
    _delta = _pb.MaxValue/((double)total);  /make sure you do not truncate delta
    _current = 0;
    _pb.Visibility = Visibility.Visible;

}
public void Dispose()
{
    _pb.Visibility = Visibility.Collapsed;
    _current = 0;
}
public void UpdateProgress()
{
    _pb.Value = (int)( _delta * (++_current)); //update after the increment
}

I suggest also using float instead of double.

0
votes

You've been saying you want to avoid using threads, I assume because you don't want unnecessary complication, but it's really not a big deal. It's a very simple matter to make an operation multi-threaded. Even for very short and simple tasks, this is the most straightforward way to achieve what you want. Using TPL, it would look something like this:

using System.Threading.Tasks;

...

Task.Factory.StartNew(() => {
    for (...) {
        // operation...
        progressBar.Dispatcher.BeginInvoke(() => progressBar.Value = ...);
    }
});