如何更新 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 Task
或 Task<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();
}
}
我的问题是,如果我只需要调用一次我的方法,如何在进度条上显示进度?我正在 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
方法必须 returnTask
或Task<T>
。 returnedTask
必须传播并等待调用链。避免在 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();
}
}