繁体   English   中英

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

[英]How can I update progress bar value in WPF MVVM

我的问题是如果我只需要调用我的方法一次,如何在进度条上显示进度? 我正在 WPF 中处理 MVVM 项目。 我必须在进度条上显示我的循环进度。 如果我在 Model 类中有类似的东西,我的 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));
        }

我的 ProgressBar 没有任何进展。 我已经检查了控制台,在这一行中我的 progressBarValue 正在正确更改但它没有发送到我的 ViewModel 类。

progressBarValue = value;

这是我的 ViewModel 类:

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

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

还要避免一些代码异味:

  • 永远不要从async方法返回void ,除非该方法是事件处理程序。 async方法必须返回TaskTask<T> 返回的Task必须在调用链上传播并等待。

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

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

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

  • 您应该尝试实施官方的 C# 命名指南

模型.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));
    });
  }
}

视图模型.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();
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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