如何更新 WPF MVVM 中的进度条值

How can I update progress bar value in WPF MVVM

我的问题是,如果我只需要调用一次我的方法,如何在进度条上显示进度?我正在 WPF 中从事 MVVM 项目。我必须在进度条上显示我的循环进度。如果我在模型 class 中有类似的东西,我的 ProgressBar 会正确更新,但只有一次

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

但是如果我尝试使用这些代码实时更新我的​​进度条:

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

我的进度条上没有任何进展。我已经在控制台上检查过,在这一行中我的 progressBarValue 正在正确更改但它没有发送到我的 ViewModel class.

progressBarValue = value;

这是我的 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));
            }
        }
    }

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

Binding.Source 必须始终引发 INotifyPropertyChanged.PropertyChanged 事件以触发 Binding 更新 Binding.Target

您应该避免通过委托 属性 绑定到 Model。模型不应公开 API 以允许此操作(数据隐藏)。
正确的模式是在 Model 中实现 View Model 可以观察到的 ProgressChanged 事件。

同时避免一些代码异味:

  • 永远不会 return void 来自 async 方法,除非该方法是事件处理程序。 async 方法必须 return TaskTask<T>。 returned Task 必须传播并等待调用链。

  • 避免在 lambda 中捕获成员变量。这在某些情况下可能会造成内存泄漏

  • 使用方法将值从 View Model 传递到 Model。将所有 Model 属性暴露给 View Model 暴露了太多的信息细节(比如要设置哪些属性来更改 Model 到有效状态)。而是让 public 方法请求所需的数据作为参数或参数对象。这些属性应该是私有的(配置对象所需的属性除外)。

  • 使用 ProgressBar...InputText... 等成员和参数名称会暗示您的模型设计不当。由于模型不知道视图,因此它对进度条或输入文本也不感兴趣。可能是简单的命名问题或严重的设计问题。

  • 你应该尝试实现官方的C#Naming Guidelines

Model.cs
ProgressChanged 事件是使用 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));
    });
  }
}

查看Model.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;
    }
  }
}

允许取消的简单异步命令实现(基于发布的示例):

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