Thursday, March 12, 2009

WPF Progress Bars Revisited

Because some people were having trouble understanding how to use my original WPF progress bar code, I thought I'd post an updated, easier to use version.

The problems people were having pretty much all centered around not understanding how to run their code in a background thread. My updated version handles this for you.

First, please have a look at the original post, as you will need some of the code from there:

WPF Progress Bars

The XAML and IProgressContext code is still the same, but here is an updated partial class for the progress dialog, along with a delegate definition that it requires:

public delegate void ProgressWorkerDelegate(IProgressContext progressContext);

public partial class ProgressDialog : Window, IProgressContext
{
private bool canceled = false;
private ProgressWorkerDelegate workDelegate = null;

public bool Canceled
{
get { return canceled; }
}

public ProgressDialog() : this(null)
{            
}

public ProgressDialog(ProgressWorkerDelegate workDelegate)
: this(workDelegate, false)
{
}

public ProgressDialog(ProgressWorkerDelegate workDelegate, bool startInBackground)
{
this.workDelegate = workDelegate;

InitializeComponent();

CancelButton.Click += new RoutedEventHandler(CancelButton_Click);

if (workDelegate != null)
{
if (startInBackground)
{
new Thread(new ThreadStart(StartWork)).Start();
}
else
{
StartWork();
}
}
}

private void StartWork()
{
workDelegate(this);
}

void CancelButton_Click(object sender, RoutedEventArgs e)
{
canceled = true;
CancelButton.IsEnabled = false;
}

public void UpdateProgress(double progress)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Progress.SetValue(ProgressBar.ValueProperty, progress); }, null);
}

public void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { StatusText.SetValue(TextBlock.TextProperty, status); }, null);
}

public void Finish()
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Close(); }, null);
}
}


The key difference is that you can now pass the dialog a delegate that will be used to do your work, and can have it automatically get run in the background.

To use it, do something like this:

ProgressDialog progressDialog = new ProgressDialog(MyWorkFunction, true);
progressDialog.ShowDialog();


Note that I am using ShowDialog() above to keep the operation modal. If you want the rest of your UI to still accept input, use Show() instead.

Your function to actually do the work would look something like this:

void MyWorkFunction(IProgressContext progressContext)
{
for (int i = 0; i < 100; i++)
{
if (myProgressContext.Canceled)
break;

progressContext.UpdateProgress((double)i / 100.0);
progressContext.UpdateStatus("Doing Step " + i);
}

progressContext.Finish();
}

26 comments:

  1. Mike, Thank you very much for the excellent tutorial. I found it very useful!

    ReplyDelete
  2. I liked your tutorial very much, Mike. Thank you very much.

    ReplyDelete
  3. I looked up more than 10 references before getting to this one, this is the only one that is clear and easy to follow. And therefore work for my case. Thanks Mike for the great explanation !

    ReplyDelete
  4. Hi, is there any possibility that you could just post a sample? i got it what you're tryin' to teach but an example would be great for newbies like me.

    ReplyDelete
  5. How do you createa ProgressDialog for a function that takes arguments?

    Example looking like this

    public void MyWorkFunction(IProgressContext progressContext, string fileName)
    {
    }

    ReplyDelete
  6. Hi Dogge,

    You can create a delegate like this:

    ProgressWorkerDelegate workDelegate = (ProgressWorkerDelegate)delegate(IProgressContext progressContext) { MyWorkFunction(progressContext, fileName); };

    ProgressDialog progressDialog = new ProgressDialog(workDelegate, true);

    ReplyDelete
  7. Thanks for the help :)

    ReplyDelete
  8. Thanks a lot! This just saved me some time. I appreciate your work!

    ReplyDelete
  9. Thanks! This has been really helpful.

    ReplyDelete
  10. I've been trying to convert this code into VB.Net and I'm stuck on one section that's giving me trouble, as I don't fully understand the C# syntax.

    Dispatcher.BeginInvoke(DispatcherPriority.Background,
    (SendOrPostCallback)delegate { Progress.SetValue(ProgressBar.ValueProperty, progress); }, null);

    Can anyone give me a hand with this? (I've tried some online code converters, but they all fail on this line)

    ReplyDelete
  11. hi..where can i download the code

    ReplyDelete
  12. Hi Anil - all of the code is in this post and my previous progress bar post. There is no separate download.

    ReplyDelete
  13. Mike, what about performance? Is there a way to make the progress bar calls not take time away from running the algorithm. Currently when I use this setup, if I leave off the progress updates, the algorithm can take 5 seconds or so to run. Adding the calls to update the progress brings the run time up to almost 30 seconds.

    ReplyDelete
  14. Hi Mike - it sounds like you are updating the progress bar too often. If you are updating it in a tight loop, then you are going to hamper performance. You really only want to update it when it will make a noticeable change.

    ReplyDelete
  15. Mike,
    Has anyone converted your code to VB? I used the code converter at Telrik to convert the original and this update for Progress Bars and was not succesful.
    Bob

    ReplyDelete
  16. Hi Bob - sorry, not that I know of.

    ReplyDelete
  17. Hi,
    I am getting error at StartWork() method, it is saying like "ProgressWorkerDelegate" has some invalid arguments, please let me know how to solve this issue?.

    Thanks
    Naga

    ReplyDelete
  18. Hi Naga. The error means just what it says. Your worker delegate does not accept the correct arguments. It needs to take a single IProgressContext argument.

    ReplyDelete
  19. Hi i'm trying yo use your code, but I get the error "Parameter Count Mismatch" can you give me some pointers of what could be happening.

    Thank you very much

    ReplyDelete
  20. Thank you for the great post.

    For the ProgressDialog, I was wondering how to change 'UpdateStatus' if we used a ListBox rather than a TextBlock. Each status update could be added to a fixed size ListBox and scroll as it updates.

    ReplyDelete
  21. I tried a few things and got it to work. I add status strings coming in to an ObservableCcollection which is databound to the ListBox.

    This seems to work:
    internal ObservableCollection Messages = new ObservableCollection();

    public void UpdateStatus(string status)
    {
    Dispatcher.BeginInvoke(DispatcherPriority.Background,
    (SendOrPostCallback)delegate {Messages.Add(status); }, null);
    }
    Then bind the ObservableCollection to the ListBox.ItemSource.

    ReplyDelete
  22. Thanks for great work..i need a progressbar for file uploader...can i go with this..Mike.
    Regards
    Amit

    ReplyDelete
  23. Hi.

    Thanks for this awesome post.

    I managed to get most of the code over to VB and was wondering if some could please help with the last 3 void's.

    Thanks :)

    ReplyDelete
  24. Nice work, helped me to create my own version of ProgressBarContext. Thank you!

    ReplyDelete
  25. this is great post ...

    ReplyDelete
  26. Nice work, but one small thing that might confuse beginners, and that is the placement of the MyWorkFunction can go in another window, such as your MainWindow, not the same window as ProgressDialog. Then you call the ProgressDialog window from this MainWindow

    // GOES IN MAINWINDOW.XAML CODE BEHIND:

    void MyWorkFunction(IProgressContext progressContext)
    {
    for (int i = 0; i < 10000; i++)
    {
    if (progressContext.Canceled != true)
    {
    progressContext.UpdateProgress((double)i / 10000.0);
    progressContext.UpdateStatus("Doing step " + i);
    }

    }
    progressContext.Finish();
    }

    private void myButton_Click(object sender, RoutedEventArgs e)
    {
    ProcessDialog PDwindow = new ProcessDialog();

    this.MyWorkFunction(PDwindow); //call the progress bar in ProgressDialog as so

    PDwindow.Show();

    }

    ReplyDelete