Nate
Nate

Reputation: 119

Using progress bar in WPF C# MVVM

My goal is to update the progress bar while another set of script (calculations) is running.

I have followed the sample files from here and tried to bind it to my MVVM script but the progress bar would not update.

enter image description here

Here is the Progressbar script

In the script below, I have included progressBarCounter and noOfDataas a value in another script that is calculated in a method.

Proof that data is updated

enter image description here

public partial class ProgressBarTaskOnWorkerThread : Window
{
    public ProgressBarTaskOnWorkerThread()
    {
        InitializeComponent();
    }

    private void Window_ContentRendered(object sender, EventArgs e)
    {
        BackgroundWorker worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;

        worker.RunWorkerAsync();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        EtabsDataFormatting.ViewModel.SpliceViewModel data = new EtabsDataFormatting.ViewModel.SpliceViewModel();

        for (int i = data.progressBarCounter; i < data.noOfData;)
        {
            (sender as BackgroundWorker).ReportProgress(i);
        }
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pbStatus.Value = e.ProgressPercentage;
        int perc = Convert.ToInt32(pbStatus.Value);
        UpdateProgress(perc);
    }

    public void UpdateProgress(int percentage)
    {
        pbStatus.Value = percentage;
        if (percentage == 100)
        {
            Close();
        }
    }
}

Here is part of my XAML code for the button to start calculations and run the progressbar

The command Binding = RunCalcBtn is bound to the calculation scripts, therefore, I have created a click to run the progress bar instead.

<Button x:Name = "ApplyButton" Margin="0 1 0 1" Content ="Start Calculation" Command="{Binding RunCalcBtn, Mode=TwoWay}" Click ="PrgBar_Click"/>

Progressbar XAML.cs button click

This part displays the progress bar, but it does not update.

private void PrgBar_Click(object sender, RoutedEventArgs e)
{
    ProgressBar.ProgressBarTaskOnWorkerThread progressWindow = new ProgressBar.ProgressBarTaskOnWorkerThread();
    progressWindow.Show();
}

Thank you so much for helping me in advance!

Upvotes: 0

Views: 6947

Answers (2)

Nguyen Van Thanh
Nguyen Van Thanh

Reputation: 825

In MVVM WPF, you should do this to take full advantage of it:

View:

<Grid>
   <ProgressBar Name="myProgressBar"
                Minimum="0" 
                Value="{Binding ProgressBarValue,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" 
                Maximum="100"
                Foreground="{Binding ColorState,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
                Background="#424242"
                BorderBrush="Transparent"
                BorderThickness="0"/>
   <TextBlock Text="{Binding ElementName=myProgressBar, Path=Value,Mode=OneWay,UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:0}%}" 
              FontWeight="DemiBold"
              HorizontalAlignment="Center"
              VerticalAlignment="Center" />
</Grid>

ViewModel:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Threading;

namespace YourNameSpace.Models
{
    public class Device : INotifyPropertyChanged
    {
        public Device()
        {
            this.ProgressBarValue = 50; // Your ProgressBar Foreground will be "GREEN" automatically
                                        // This is the 
        }
        
        private double progressBarValue;
        public double ProgressBarValue
        {
            get { return progressBarValue; }
            set 
            { 
                progressBarValue = value; 
                if(progressBarValue < 50)
                    this.ColorState = "Red";
                else if (progressBarValue >= 50)
                    this.ColorState = "Green";
                NotifyPropertyChanged("ProgressBarValue"); 
            }
        }
        private string colorState = "Transparent";
        public string ColorState
        {
            get { return colorState; }
            set { colorState = value; NotifyPropertyChanged("ColorState"); }
        }
        
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string Obj)
        {
            if (PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(Obj));
            }
        }
    }
}

You can REMOVE this from your code:

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pbStatus.Value = e.ProgressPercentage;
        int perc = Convert.ToInt32(pbStatus.Value);
        UpdateProgress(perc);
    }

    public void UpdateProgress(int percentage)
    {
        pbStatus.Value = percentage;
        if (percentage == 100)
        {
            Close();
        }
    }

And ONLY use this:

for (int i = data.progressBarCounter; i < 100; i++)
{
    ProgressBarValue = i;
}

Your

ProgressBar Value

Progress Foreground Color

will be updated automatically.

Upvotes: 0

Jonathan Willcock
Jonathan Willcock

Reputation: 5245

As Flithor has said, the best way to achieve this is with Progress<T>.

I give a short illustration of how to use this.

Firstly you need to create a Property in your View Model so that you can bind the ProgressBar's Value to something. Your ViewModel will need to implement INotifyPropertyChanged so that the Property set can invoke RaisePropertyChangedEvent.

Next create a Progress inside the method called by the Button click and pass it to your worker method. Use an ICommand for this, so that it can be bound to your Button (you don't need the Click event). Something like this:

var progress = new Progress<int>(percent =>
    {
        ProgressProperty = percent;
    });

await Task.Run(() => myWorker(progress));

Finally within your worker method you periodically update the value like this:

private void myWorker(IProgress<int> progress)
{
    progress.Report(1);
    // ...
    progress.Report(100);
}

By way of explanation: I used an integer, but you can also use a double if you want really fine calculations! The constructor of the Progress object takes the ProgressProperty (the name I gave to the property that gets bound to the ProgressBar) as a parameter. This means that when the worker calls Report(), the ProgressProperty is automatically updated with the new value, and hence can be reflected in the UI. Finally your worker method is invoked with await so that the UI is able to update on every incremented value.

For a very full explanation on Progress, see Stephen Cleary's blog

Upvotes: 2

Related Questions