My question is how go show progress on progressbar if I have to call my method only once? I am working on MVVM project in WPF. I have to display progress of my loop on the progressBar. If i have got something like this in Model class, my ProgressBar is updating correctly, but only once
public double progressBarValue;
public string inputText;
public int quantityOfNumbers
{
get;
set;
}
public void inputTextToInt(string inputText)
{
progressBarValue = Convert.ToInt32(inputText);
quantityOfNumbers = Convert.ToInt32(inputText);
//work();
}
But if I've tried to updating in real time my progressbar, with these code:
public void inputTextToInt(string inputText)
{
//progressBarValue = Convert.ToInt32(inputText);
quantityOfNumbers = Convert.ToInt32(inputText);
work();
}
private async void work()
{
RandNumbers randNumbers = new RandNumbers(quantityOfNumbers);
var progress = new Progress<double>(value =>
{
progressBarValue = value;
Console.WriteLine(progressBarValue);
});
await Task.Run(() => randNumbers.RandAllNumbers(progress));
}
There isn't any progress on my ProgressBar. I've checked on console and in this line my progressBarValue is changing correctly But it isn't sending to my ViewModel class.
progressBarValue = value;
And this is my ViewModel class:
private ModelTME_App model = new ModelTME_App();
public string inputText
{
get { return model.inputText; }
set { model.inputText = value;
onPropertyChanged(nameof(inputText));
}
}
public double progressBarValue
{
get {
Console.WriteLine(model.progressBarValue);
return model.progressBarValue;
}
}
private ICommand inputTextToInt = null;
public ICommand InputTextToInt
{
get
{
if(inputTextToInt == null)
{
inputTextToInt = new RelayCommand(
(object o) =>
{
model.inputTextToInt(inputText);
onPropertyChanged(nameof(progressBarValue));
},
(object o) =>
{
return model.progressBarValue == 0;
});
}
return inputTextToInt;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string nameOfProperty)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameOfProperty));
}
}
}
My RelayCommandClass
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
else
{
this.execute = execute;
}
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested = value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
if(canExecute == null)
{
return true;
}
else
{
return canExecute(parameter);
}
}
public void Execute(object parameter)
{
execute(parameter);
}
}
CodePudding user response:
The Binding.Source
must always raise the INotifyPropertyChanged.PropertyChanged
event to trigger the Binding
to update update the Binding.Target
.
You should avoid binding to the Model via a delegating property. The Model shouldn't expose an API to allow this (data hiding).
The proper pattern would be to implement a ProgressChanged
event in the Model that the View Model can observe.
Also avoid a few code smells:
Never return
void
from anasync
method except the method is an event handler. Anasync
method must returnTask
orTask<T>
. The returnedTask
must be propagated and awaited up the call chain.Avoid capturing member variable in lambdas. This can create a memory leak in certain situations
Pass values from View Model to Model using methods. Exposing all Model properties to the View Model exposes too much information details (like which properties to set to change the Model to a valid state). Instead make public method request the required data as parameters or parameter objects. The properties should be private (except those required to configure the object).
Using member and parameter names like
ProgressBar...
orInputText...
gives a hint that your model is not properly designed. Since the model is unaware of the view, it is not interested in progress bars or input text too. Could be a simple naming issue or a serious design issue.You should try to implement the official C# Naming Guidelines
Model.cs
The ProgressChanged
event is defined using the ProgressChangedEventArgs
.
class Model
{
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
private double ProgressPercentage { get; set; }
private string NumericText { get; set; }
private int QuantityOfNumbers { get; set; }
protected virtual void OnProgressCHanged(double percentage)
=> this.ProgressChanged?.Invoke(new ProgressChangedEventArgs(percentage, string.Empty);
public async Task<int> ToIntAsync(string numericText)
{
NumericText = numericText;
QuantityOfNumbers = Convert.ToInt32(NumericText);
await WorkAsync();
return QuantityOfNumbers;
}
private async Task WorkAsync()
{
// Can you guarantee that this method is called from the UI thread?
var progress = new Progress<double>(value =>
{
ProgressPercentage = value;
Console.WriteLine(ProgressBarValue);
OnProgressChanged(ProgressBarValue);
});
// Avoid capturing member variables in lambdas
int quantityOfNumbers = QuantityOfNumbers;
await Task.Run(() =>
{
var randNumbers = new RandNumbers(quantityOfNumbers);
randNumbers.RandAllNumbers(progress));
});
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private Model _model;
// TODO::Let property raise PropertyChanged event
public double ProgressValue { get; set; }
public ICommand InputTextToIntCommand => new RelayCommand(ExecuteInputTextToIntCommandAsync);
// TODO::Let property raise PropertyChanged event
public string NumericText { get; set;}
public ViewModel()
{
_model = new Model();
}
// Make sure RelayCommand can execute async delegates
private async Task ExecuteInputTextToIntCommandAsync()
{
_model.PropgressChnaged = OnProgressChanged;
// Pass value to model
var quantityOfNumbersResult = await _model.ToIntAsync(this.NumericText);
}
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.ProgressValue = e.ProgressPercentage;
if (this.ProgressValue >= 100)
{
_model.PropgressChnaged -= OnProgressChanged;
}
}
}
A simple async command implementation that allows cancellation (based on the posted example):
public class RelayCommand : ICommand
{
private CancellationTokenSource cancellationTokenSource;
private readonly Func<object, Task> executeAsync;
private readonly Func<object, CancellationToken, Task> executeCancellableAsync;
private Action<object> execute;
private Func<object, bool> canExecute;
public RelayCommand(Func<object, CancellationToken, Task> executeCancellableAsync, Func<object, bool> canExecute)
{
this.executeCancellableAsync = executeCancellableAsync;
this.canExecute = canExecute;
}
public RelayCommand(Func<object, Task> executeAsync, Func<object, bool> canExecute)
{
this.executeAsync = executeAsync;
this.canExecute = canExecute;
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested = value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
_ = ExecuteAsync(parameter);
}
public async Task ExecuteAsync(object commandParameter)
{
using (this.cancellationTokenSource = new CancellationTokenSource())
{
await ExecuteAsync(commandParameter, this.cancellationTokenSource.Token);
}
}
public Task ExecuteAsync(object commandParameter, CancellationToken cancellationToken)
{
if (this.executeCancellableAsync is not null)
{
return this.executeCancellableAsync.Invoke(commandParameter, cancellationToken);
}
else if (this.executeAsync is not null)
{
return this.executeAsync.Invoke(commandParameter);
}
else
{
this.execute.Invoke(commandParameter);
return Task.CompletedTask;
}
}
public void Cancel()
{
this.cancellationTokenSource?.Cancel();
}
}
CodePudding user response:
To update your progress bar in realtime within your loop:
public double progressBarValue;
progressBarValue = 0;
int len = array.Length;
for(int i=0; i<len; i ) {
//do something
//for end of every iteration
progressBarValue = (double)i / len * 100;
work();
}