简体   繁体   中英

How can I update progress bar value in WPF MVVM

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

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 an async method except the method is an event handler. An async method must return Task or Task<T> . The returned Task 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... or InputText... 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();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM