我正在尝试找到一种使用 ViewModel 中创建的线程来运行现有方法的正确方法。主要目的是提供响应式 UI。我决定使用基于任务的异步模式,但我需要将其与 WPF 和 MVVM 正确集成。
到目前为止,我找到了一种在另一个线程中运行冗长任务并报告其进度的方法。但是,我找不到更新启动任务的按钮的方法,以便仅在任务未运行时才启用它。以下 ViewModel 描述了我所做的事情:
public class MainViewModel : INotifyPropertyChanged
{
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
// Do some time consuming work.
int SomeTask()
{
//SCENARIO: Consider that it takes longer than usual to start the worker thread.
Thread.Sleep(1000);
// Prevent executing the task by two threads at the same time.
lock ("IsReady")
{
if (IsReady == false)
throw new ApplicationException("Task is already running");
IsReady = false;
}
// The actual work that this task consists of.
TaskProgress = 0;
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
TaskProgress = i;
}
// Mark task as completed to allow rerunning it.
IsReady = true;
return 123;
}
// True when not started or completed.
bool _isReady = true;
public bool IsReady
{
get { return _isReady; }
set
{
_isReady = value;
NotifyPropertyChanged("IsReady");
StartTaskCommand.RaiseCanExecuteChanged();
}
}
// Indicate the current progress when running SomeTask.
int _taskProgress;
public int TaskProgress
{
get { return _taskProgress; }
set
{
_taskProgress = value;
NotifyPropertyChanged("TaskProgress");
}
}
// ICommand to start task asynchronously.
RelayCommand _startTask;
public RelayCommand StartTaskCommand
{
get
{
if (_startTask == null)
{
_startTask = new RelayCommand(
obj =>
{
Task<int> task = Task.Run((Func<int>)SomeTask);
task.ContinueWith(t =>
{
// SomeTask method may throw an ApplicationException.
if (!t.IsFaulted)
Result = t.Result.ToString();
});
},
obj => IsReady);
}
return _startTask;
}
}
string _result;
public string Result
{
get { return _result; }
set { _result = value; NotifyPropertyChanged("Result"); }
}
}
我使用以下 RelayCommand 实现:
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
主要问题是执行命令的 Button 不会根据 IsReady
更新其状态。我还尝试使用 IsEnabled="{Binding IsReady}"
显式设置它,但它仍然不起作用。我发现与此问题相关的最好的文章是:Raising CanExecuteChanged .
XAML 非常简单:
<DockPanel Margin="4">
<TextBox DockPanel.Dock="Right" Text="{Binding Result}" Width="100"/>
<Button DockPanel.Dock="Right" Content="Start" Margin="5,0"
Command="{Binding StartTaskCommand}"/>
<ProgressBar Value="{Binding TaskProgress}"/>
</DockPanel>
如何修复 IsReady 以由按钮的状态反射(reflect)?
有人推荐一个满足我需要的简约工作实现吗?
感谢您花时间阅读。
最佳答案
在启动任务之前,将 UI 线程上的 IsReady
属性设置为 false
,然后在任务完成后将其设置回 true
已完成:
public RelayCommand StartTaskCommand
{
get
{
if (_startTask == null)
{
_startTask = new RelayCommand(
obj =>
{
if (IsReady)
{
//1. Disable the button on the UI thread
IsReady = false;
//2. Execute SomeTask on a background thread
Task.Factory.StartNew(SomeTask)
.ContinueWith(t =>
{
//3. Enable the button back on the UI thread
if (!t.IsFaulted)
Result = t.Result.ToString();
IsReady = true;
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
},
obj => IsReady);
}
return _startTask;
}
}
int SomeTask()
{
Thread.Sleep(1000);
TaskProgress = 0;
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
TaskProgress = i;
}
return 123;
}
关于c# - WPF ViewModel在另一个线程中运行方法,报告进度,更改Button的IsEnabled并显示结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48687766/