簡體   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