44

I have a WPF application which is built on the MVVM design pattern.

I wish to implement a progress bar in the app, that follows the MVVM pattern.

Does any one have any suggestions on how to implement this?

Thanks in advance

jpgooner
  • 731
  • 2
  • 7
  • 12

3 Answers3

69

Typically your UI would simply bind to properties in your VM:

<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}" 
             Visibility="{Binding ProgressVisibility}"/>

Your VM would use a BackgroundWorker to do the work on a background thread, and to periodically update the CurrentProgress value. Something like this:

public class MyViewModel : ViewModel
{
    private readonly BackgroundWorker worker;
    private readonly ICommand instigateWorkCommand;
    private int currentProgress;

    public MyViewModel()
    {
        this.instigateWorkCommand = 
                new DelegateCommand(o => this.worker.RunWorkerAsync(), 
                                    o => !this.worker.IsBusy);

        this.worker = new BackgroundWorker();
        this.worker.DoWork += this.DoWork;
        this.worker.ProgressChanged += this.ProgressChanged;
    }

    // your UI binds to this command in order to kick off the work
    public ICommand InstigateWorkCommand
    {
        get { return this.instigateWorkCommand; }
    }

    public int CurrentProgress
    {
        get { return this.currentProgress; }
        private set
        {
            if (this.currentProgress != value)
            {
                this.currentProgress = value;
                this.OnPropertyChanged(() => this.CurrentProgress);
            }
        }
    }

    private void DoWork(object sender, DoWorkEventArgs e)
    {
        // do time-consuming work here, calling ReportProgress as and when you can
    }

    private void ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.CurrentProgress = e.ProgressPercentage;
    }
}
Akram Shahda
  • 14,655
  • 4
  • 45
  • 65
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • Hi Kent, would I need a seperate model and view model for the progress bar? or would I add a progress bar to each existing view where i intent to use it? – jpgooner Aug 19 '10 at 09:43
  • I would start simple by using one per view and then decide whether you can abstract that into a reusable component. – Kent Boogaart Aug 19 '10 at 09:44
  • 3
    Thanks, very helpful example! Two things I just found during test are worth to be noted: 1) Either the setter of `CurrentProgress` must be public or one has to specify `OneWay` mode binding explicitly on the Progressbar. The default seems to be TwoWay (strange for a Progressbar), so the binding engine throws an exception with a private setter. 2) When using `ReportProgress` it seems to be necessary to set the `WorkerReportsProgress` property of the `BackgroundWorker` to `true`. Otherwise the thread execution stops silently (no exception, only visible in the `RunWorkerCompleted` event args). – Slauma Mar 01 '11 at 14:44
  • 2
    @Kent Boogaart, after writing such string:" this.instigateWorkCommand = new DelegateCommand(o => this.worker.RunWorkerAsync, o => !this.worker.IsBusy);" . VS2010 throws an error: "delegate 'system.action' does not take 1 arguments". Could you tell how to get over it? – StepUp Feb 02 '14 at 12:14
  • @KentBoogaart, after writing such string:" this.instigateWorkCommand = new DelegateCommand(o => this.worker.RunWorkerAsync, o => !this.worker.IsBusy);" . VS2010 throws an error: "delegate 'system.action' does not take 1 arguments". Could you tell how to get over it? – StepUp Feb 22 '14 at 02:18
  • @KalaJ It looks like the RelayCommand that is available in many MVVM frameworks. Anyway, you simply pass Execute and CanExecute delegates to an instance of that implementation of ICommand and let it handle the rest. Google for RelayCommand to find otu more about it. – Ondrej Janacek Jul 30 '14 at 07:02
  • Delegate Sys.Action does not take 1 arguments. Error is thrown. How to solve this? – A Coder Jun 05 '15 at 11:53
11

Use a ProgressBar control and bind its Value property to a property of the ViewModel:

View

<ProgressBar Minimum="0" Maximum="0" Value="{Binding CurrentProgress}" />

ViewModel

private double _currentProgress;
public double CurrentProgress
{
    get { return _currentProgress; }
    private set
    {
        _currentProgress = value;
        OnPropertyChanged("CurrentProgress");
    }
}
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • How do I connect this with my Command? That is, I have a button and on click, I want to start the progress bar until the action is done. – Kala J Jul 28 '14 at 15:02
  • @KalaJ, you just need to update the CurrentProgress property in your action (e.g. if it's a loop, update the value at every iteration) – Thomas Levesque Jul 28 '14 at 15:34
  • The thing is I am just waiting for the system to read/detect a CD when I insert a CD into my computer. Sometimes it takes the system a while to read the CD other times it doesn't. How do I update a value like such? I was thinking maybe using an indeterminate progress bar but I'm still confused on how to connect it to my Command? – Kala J Jul 28 '14 at 17:25
  • 1
    @KalaJ, in that case you can't display a real progress, since you don't know how close the action is to complete. You can set `IsIndeterminate` to true in XAML, and use a bool property in the ViewModel to hide/show the progress bar. – Thomas Levesque Jul 28 '14 at 17:28
  • OK, in this case, would I need a value property? – Kala J Jul 28 '14 at 17:34
  • @KalaJ, no, since you don't have a sensible value to provide. Just don't bind the `Value` property at all. – Thomas Levesque Jul 28 '14 at 17:39
  • Ok, one last thing, should I set the visibility in the command or method? It seems like there's still a delay when I set the visibility to collapsed inside my method instead of command? – Kala J Jul 28 '14 at 18:04
  • @KalaJ, what do you mean? Typically (when you use RelayCommand or DelegateCommand), the command doesn't really do anything, it just calls a method. – Thomas Levesque Jul 28 '14 at 20:23
  • Actually, it seems like I need to use Background Worker because the progress bar hangs/gets stuck until the operation is done. – Kala J Jul 29 '14 at 15:15
  • @KalaJ, if you're doing the work on the UI thread, the ProgressBar can't be updated, so indeed you need to offload the work to another thread. – Thomas Levesque Jul 29 '14 at 15:20
  • http://stackoverflow.com/questions/25021838/indeterminate-progress-bar Still stuck.... Not sure how to proceed. Thanks for your help and sorry for the trouble. – Kala J Jul 29 '14 at 17:52
6

Add two properties to your VM:

bool IsProgressBarVisible
double ProgressValue

If you start a long time operation in your VM, set the IsProgressBarVisible-property to true and set the ProgressValue periodical to the current progress value. Try to calculate a value between 0 and 100. This has the advantage that you don't have to provide a minimum and maximum value. After the asynchronous operation has completed, set the IsProgressBarVisible to false.

In XAML, bind to these two properties. Use a value converter to convert the boolean visibility to a Visibility.

<ProgressBar Value="{Binding ProgressValue}" Visibility="{Binding IsProgressBarVisible,Converter={StaticResource BooleanToVisibility_ValueConverter}}"/> 
HCL
  • 36,053
  • 27
  • 163
  • 213