How to implement a progress bar using the MVVM pattern - c#

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

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;
}
}

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");
}
}

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}}"/>

Related

BackgroundWorker not updating UI

Before I get into the details here, I'm still in what I would consider to be the "learning" phase of my C#/WPF journey... so apologies if what I'm asking here is stupidly obvious...
I have a small application (WPF, .NET Framework 4.8) that does the following:
read a list of values
do something for each value in the list
I am trying to do this with a BackgroundWorker so that I can report back to the UI as the list is being processed, preferably with a progress bar.
For the moment, the DoWork method just has some code in there to indicate that it's actually going through the process as expected and so that I could check that all the UI is updating as expected before I put the actual "what I want it to do" in there.
It seems that all the properties are updating as expected, but the UI (i.e. the progress bar) just doesn't move.
And I have checked that the data context in the XAML is set correctly (both in the XAML and in the Code-Behind).
In my XAML I have the following:
<ProgressBar x:Name="ProgressBar"
Width="740"
Height="20"
Background="Transparent"
Foreground="#008DEB"
Grid.Row="3"
Minimum="0"
Maximum="100"
Value="{Binding ProgressBarIndicator}"/>
And in my class containing all my methods/properties etc, I have:
private int _measurementProgress;
public int MeasurementProgress
{
get { return _measurementProgress; }
set
{
_measurementProgress = value;
OnPropertyChanged();
}
}
private int _progressBarIndicator;
public int ProgressBarIndicator
{
get { return _progressBarIndicator; }
set
{
_progressBarIndicator = value;
OnPropertyChanged();
}
}
public void StartMeasurements(string ipAddress)
{
TotalMeasurementsInList = CommandsList.Count;
MeasurementProgress = 0;
measurementWorker.WorkerReportsProgress = true;
measurementWorker.DoWork += worker_DoWork;
measurementWorker.ProgressChanged += worker_ProgressChanged;
measurementWorker.RunWorkerCompleted += worker_RunWorkerCompleted;
measurementWorker.RunWorkerAsync();
}
public void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var command in CommandsList)
{
MessageBox.Show(String.Format("Measuring Sample: {0}",command.SampleName),"Measuring Sample");
measurementWorker.ReportProgress((int)((double)(MeasurementProgress / (double)TotalMeasurementsInList)*100));
Thread.Sleep(command.DelayTime*1000);
}
}
public void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
MeasurementProgress++;
MessageBox.Show(String.Format("Progress is {0}%", e.ProgressPercentage.ToString()));
ProgressBarIndicator = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBarIndicator = 100;
MessageBox.Show("Measurements are completed","Finished");
}
The "OnPropertyChanged()" method is inside my ObservableObject class, and the above class is set to inherit from this ObservableObject class. The ObservableObject class looks like this:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
By placing break-points in strategic places, I can confirm that the value of MeasurementProgress does indeed increment as one would expect, as does the value of ProgressBarIndicator. This was double-confirmed by the MessageBox in the worker_ProgressChanged method as it does indeed display the appropriate percentage value.
Annoyingly, what would appear to me to be exactly the same code is working as expected in another part of the application. The code here is more or less a copy/paste from there... but I just can't see what I'm doing wrong.
Any help or pointers where I could look to try and debug this appreciated.
Many thanks
Colin

How do I update ProgressBar from the Model in MVVM?

I have a ProgressBar in my MVVM View, which is bound to a View Model property. Updating the property in the VM all works correctly. However, I have some longer-running file/network operations which take place in another class (Model), and I would like to update the ProgressBar property in the middle of the Model operations. I can't pass the ProgressBar property via reference to the Model class. I definitely don't want to pass a handle to the VM to the Model. How do I update this VM property from the Model classes adData and fileOps?
Edit: Additional code to show where I need to update the ProgressBar property.
View
<ProgressBar Value="{Binding ProgressMeter}"/>
<TextBlock Text="{Binding CurrentStatusMsg}"/>
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private readonly IADData adData;
private readonly IFileOps fileOps;
public ViewModel(IADData adData, IFileOps fileOps)
{
this.adData = adData;
this.fileOps = fileOps;
}
// INPC Implementation goes here
private int progressMeter;
public int ProgressMeter
{
get => progressMeter;
set
{
if (progressMeter != value)
{
progressMeter = value;
RaisePropertyChanged("ProgressMeter");
}
}
}
// Similar property for CurrentStatusMsg
public void DoIt()
{
BackgroundWorker bgWorker = new BackgroundWorker
{
WorkerReportsProgress = true
};
bgWorker.DoWork += CreatePhoneList;
bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
CurrentStatusMsg = "Creating Phone List...";
ProgressMeter = 5;
bgWorker.RunWorkerAsync();
}
private void CreatePhoneList(object sender, DoWorkEventArgs e)
{
// How do I update ProgressMeter in adData and fileOps classes?
DataTable t = adData.ReportLines();
fileOps.AddDeptRows(t);
e.Result = t;
}
private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressMeter = 100;
CurrentStatusMsg = "Creating Phone List... Complete.";
reportCreator.ShowReport((DataTable)e.Result);
}
}
Thanks to #Adam Vincent, I ended up using the Messenger Pattern - specifically this implementation. I can now pass the ProgressBar value from the Model to the View, without breaking abstraction layers.

Progress-bar MVVM?

I am having a little bit of trouble getting a ProgressBar to work. When I start it up, nothing happens and I can't see why?
I thought that this would start the task worker.RunWorkerAsync();
The below example should be able to be copied and pasted into a new solution and be run for testing if needed.
The XAML,
<Grid Margin="20">
<ProgressBar
Height="60"
Minimum="0"
Maximum="100"
Value="{Binding Progress, Mode=OneWay}"
Visibility="{Binding ProgressVisibility}"/>
</Grid>
My code,
public partial class MainWindow : Window
{
public ProggressbarViewModel PsVm { get; set; }
public MainWindow()
{
InitializeComponent();
PsVm = new ProggressbarViewModel();
}
public class ProggressbarViewModel
{
public ProggressbarViewModel()
{
var worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
}
private int _progress;
public int Progress
{
get { return _progress; }
set
{
if (_progress == value) return;
_progress = value;
OnPropertyChanged();
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
_progress = i;
OnPropertyChanged();
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress = e.ProgressPercentage;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Any help would be much appreciated.
EDIT: The question is is similar possibly a duplicate in that sense, however the linked answer did not solve my problem, like it states in the Duplicate banner.
When you're not explicitly indicating source object for your bindings (by means of Binding.Source or Binding.RelativeSource properties), the framework uses (possibly inherited) value of DataContext of the target object as the source. The problem is that you don't assign your view-model to the DataContext property of any control. Thus, the source for the bindings resolves to null and nothing is showing on your progress bar.
To resolve your issue you should assign your view model to the DataContext of your MainWindow:
public MainWindow()
{
InitializeComponent();
PsVm = new ProggressbarViewModel();
DataContext = PsVm;
}
If however you're planning on using different DataContext for your window, you can bind DataContext of ProgressBar:
<ProgressBar
DataContext="{Binding Path=PsVm,
RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
(...) />
You could also modify particular bindings by prepending PsVm. to the value of Path and using RelativeSource, e.g.:
Value="{Binding Path=PsVm.Progress,
RelativeSource={RelativeSource AncestorType=local:MainWindow},
Mode=OneWay}"
In that case however you'd have to modify each binding, so previous solutions are quicker and/or simpler.
As a side note, you might also want to change the way you're reporting progress - note that OnPropertyChanged in your DoWork method is not called from UI thread. The proper way to do it would be:
private void DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
worker.ReportProgress(i); //This will raise BackgroundWorker.ProgressChanged
}
}
Also, in order to support progress reporting, you should set WorkerReportsProgress to true on your worker, e.g.:
var worker = new BackgroundWorker { WorkerReportsProgress = true };

Use ICommand in WPF

What is the best way to use commands in WPF ?
I use some commands, thoses commands can take a time to execute. I want that my application not freeze while running but I want the features to be disabled.
there is my MainWindow.xaml :
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Button Grid.Row="0"
Grid.Column="0"
Style="{StaticResource StyleButton}"
Content="Load"
Command="{Binding LoadCommand}"/>
<Button Grid.Row="0"
Grid.Column="1"
Style="{StaticResource StyleButton}"
Content="Generate"
Command="{Binding GenerateCommand}"/>
</Grid>
</Window>
and my MainViewModel.cs :
public class MainViewModel : ViewModelBase
{
#region GenerateCommand
#endregion
#region Load command
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
//My code
}
private bool CanLoad()
{
return true;
}
#endregion
}
I saw a solution with background worker but I don't know how to use it. And I wonder if I should create one instance by command.
Is there a cleaner/best way ?
I want that my application not freeze while running but I want the features to be disabled.
The key to prevent the application from freezing is to perform any long-running operation on a background thread. The easiest way to do this is to start a Task. To disable the window you could bind its IsEnabled property to a source property of the view model that you set prior to starting the task. The following sample code should give you the idea:
public class MainViewModel : ViewModelBase
{
private RelayCommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
IsEnabled = false;
_canLoad = false;
_loadCommand.RaiseCanExecuteChanged();
Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); }) //simulate som long-running operation that runs on a background thread...
.ContinueWith(task =>
{
//reset the properties back on the UI thread once the task has finished
IsEnabled = true;
_canLoad = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
private bool _canLoad = true;
private bool CanLoad()
{
return _canLoad;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; RaisePropertyChanged(); }
}
}
Note that you cannot access any UI element from a background thread since controls have thread affinity: http://volatileread.com/Thread/Index?id=1056
My approach to avoid UI freezing in these scenarios is to use async/await in the ICommand execution, and execute the long-running code on a background thread. Your modified code would look something like this:
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad);
return _loadCommand;
}
}
private async Task OnLoadAsync()
{
await Task.Run(() => MyLongRunningProcess());
}
If that background task needs to update anything bound to the UI then it needs to be wrapped in a Dispatcher.Invoke (or Dispatcher.BeginInvoke).
If you want to prevent the command from being executed a second time just set "CanLoad" to true before the await Task.Run(... line, and back to false after it.
I'd suggest to use Akka.Net: you can find an example with WPF on github.
I've forked it to impement stop and start commands:
my goal was to show bidirectional communication between Akka.Net actors and ViewModel.
You'll find the ViewModel calling the ActorSystem like this
private void StartCpuMethod() {
Debug.WriteLine("StartCpuMethod");
ActorSystemReference.Start();
}
private void StopCpuMethod() {
Debug.WriteLine("StopCpuMethod");
ActorSystemReference.Stop();
}
with an Actor receiving those messages
public CPUReadActor()
{
Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage());
Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg));
}
private void ReceiveSyncMessage(ReadCPUSyncMessage msg)
{
switch (msg.Op)
{
case SyncOp.Start:
OnCommandStart();
break;
case SyncOp.Stop:
OnCommandStop();
break;
default:
throw new Exception("unknown Op " + msg.Op.ToString());
}
}
and the other way round from an Actor
public ChartingActor(Action<float, DateTime> dataPointSetter)
{
this._dataPointSetter = dataPointSetter;
Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg));
}
private void ReceiveDrawPointMessage(DrawPointMessage msg)
{
_dataPointSetter(msg.Value, msg.Date);
}
to the ViewModel
public MainWindowViewModel()
{
StartCpuCommand = new RelayCommand(StartCpuMethod);
StopCpuCommand = new RelayCommand(StopCpuMethod);
SetupChartModel();
Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d));
ActorSystemReference.CreateActorSystem(dataPointSetter);
}
private void SetDataPoint(float value, DateTime date)
{
CurrentValue = value;
UpdateLineSeries(value, date);
}
The best way here it's a use of async/await, in my opinion. https://msdn.microsoft.com/ru-ru/library/mt674882.aspx
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad);
}
public ICommand LoadCommand { get; }
private async void OnLoadAync()
{
await SomethingAwaitable();
}
private Task<bool> SomethingAwaitable()
{
//Your code
}
}

Update property's value on another Window in WPF

There is a property in my ViewModel whose value is changed in the DoWork method of the BackgroundWorker. When I start the application and click on the button that starts the BackgroundWorker, I see how the value of this property changes. However, when I open a new window, this property retains its default value and is not updated even though the BackgroundWorker is still running.
Her is the code in my ViewModel:
private string currentData;
...
public ViewModel()
{
...
// Property initialised with a default value
currentData = "BackgroundWorker is not running";
...
}
public string CurrentData
{
get { return this.currentData; }
private set
{
if (this.currentData != value)
{
this.currentData = value;
this.RaisePropertyChanged("CurrentData");
}
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
isUpdating = true;
...
this.CurrentData = "BackgroundWorker is running...";
for (...)
{
...
if(...)
{
this.CurrentData = "value1";
}
else
{
this.CurrentData = "value2";
...
}
}
}
RaisePropertyChanged Method:
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML-code for both windows (MainWindow and newtWindow):
<TextBlock Margin="10" MinWidth="250" VerticalAlignment="Center" Text="{Binding CurrentData}" FontSize="12" Foreground="White" HorizontalAlignment="Left" />
BackgroundWorker:
private readonly BackgroundWorker worker;
...
public ImageViewModel()
{
currentData = "BackgroundWorker is not running";
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completeted);
this.worker.WorkerReportsProgress = true;
}
Can you tell what I'm doing wrong and how I can fix it?
You would have to create a private string reference out of your property,
where the property can set the value and it will be saved on the stack,
something like so(this is how wpf get info from text boxes in the text property)
private string _text; //string that is used as a reference which you can plug your new new window
public string Text
{
get
{
return this._text;
}
set
{
this._text = value;
if (null != PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs ("Text"));
}
}
}
I would avoid updating a property, which is bound to the UI, from a background thread. I'm not sure if this will solve your issue, but I would try to use the BackgroundWorker's ReportProgress method to notify your ViewModel about changes of CurrentData. Then in the OnProgressChanged event handler you can set the CurrentData to a new String.
public void ReportProgress(int percentProgress, object userState)
You can put your String into the "userState" object.
Edit
something like this:
public ViewModel()
{
...
backgroundWorker.ReportsProgress = true;
backgroundWorker.ProgressChanged += OnProgressChanged;
...
}
private void DoWork(object sender, DoWorkEventArgs e)
{
isUpdating = true;
...
ReportProgress(0,"BackgroundWorker is running...");
for (...)
{
...
if(...)
{
ReportProgress(0,"value1");
}
else
{
ReportProgress(0,"value2");
...
}
}
}
and
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentData = (string)e.UserState;
}
Ok so from what you've said so far my understanding is as follows:
From your original question:
However, when I open a new window, this property retains its default value and is not updated even though the BackgroundWorker is still running.
From your comment to my previous answer about setting the window's DataContext:
<Window.DataContext> <local:ViewModel /> </Window.DataContext>
When you create a new window, you also create a new instance of your ViewModel. This new instance also has its own BackgroundWorker. When you say "...even though the BackgroundWorker is still running", then this is only true for your first window, since the Backgroundworker from your new window has to be started first.
If you want the same DataContext (and thus the same BackgroundWorker) for both windows, you need to set the DataContext of your new window to the already existing instance of your ViewModel.

Categories

Resources