Используйте ICommand в WPF
Каков наилучший способ использовать команды в WPF?
Я использую некоторые команды, эти команды могут занять некоторое время для выполнения. Я хочу, чтобы мое приложение не зависало во время работы, но я хочу, чтобы функции были отключены.
есть мой MainWindow.xaml:
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Button Grid.Row="0"
Grid.Column="0"
Style="{StaticResource StyleButton}"
Content="Load"
Command="{Binding LoadCommand}"/>
<Button Grid.Row="0"
Grid.Column="1"
Style="{StaticResource StyleButton}"
Content="Generate"
Command="{Binding GenerateCommand}"/>
</Grid>
</Window>
и мой MainViewModel.cs:
public class MainViewModel : ViewModelBase
{
#region GenerateCommand
#endregion
#region Load command
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
//My code
}
private bool CanLoad()
{
return true;
}
#endregion
}
Я видел решение с фоновым работником, но я не знаю, как его использовать. И мне интересно, если я должен создать один экземпляр по команде.
Есть ли чище / лучший способ?
4 ответа
Я хочу, чтобы мое приложение не зависало во время работы, но я хочу, чтобы функции были отключены.
Ключом к предотвращению зависания приложения является выполнение любой длительной операции в фоновом потоке. Самый простой способ сделать это - запустить задание. Чтобы отключить окно, вы можете привязать его свойство IsEnabled к исходному свойству модели представления, которую вы установили перед запуском задачи. Следующий пример кода должен дать вам идею:
public class MainViewModel : ViewModelBase
{
private RelayCommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
IsEnabled = false;
_canLoad = false;
_loadCommand.RaiseCanExecuteChanged();
Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); }) //simulate som long-running operation that runs on a background thread...
.ContinueWith(task =>
{
//reset the properties back on the UI thread once the task has finished
IsEnabled = true;
_canLoad = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
private bool _canLoad = true;
private bool CanLoad()
{
return _canLoad;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; RaisePropertyChanged(); }
}
}
Обратите внимание, что вы не можете получить доступ к любому элементу пользовательского интерфейса из фонового потока, так как элементы управления имеют сходство потоков: http://volatileread.com/Thread/Index?id=1056
Мой подход, позволяющий избежать зависания пользовательского интерфейса в этих сценариях, заключается в использовании async / await при выполнении ICommand и выполнении долговременного кода в фоновом потоке. Ваш модифицированный код будет выглядеть примерно так:
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad);
return _loadCommand;
}
}
private async Task OnLoadAsync()
{
await Task.Run(() => MyLongRunningProcess());
}
Если этой фоновой задаче необходимо обновить что-либо, связанное с пользовательским интерфейсом, то ее нужно обернуть в Dispatcher.Invoke
(или же Dispatcher.BeginInvoke
).
Если вы хотите предотвратить выполнение команды во второй раз, просто установите для "CanLoad" значение true, прежде чем await Task.Run(...
линия и обратно к ложному после нее.
Я бы предложил использовать Akka.Net: вы можете найти пример с WPF на github.
Я раздвоил это, чтобы помешать командам остановки и запуска: моей целью было показать двунаправленную связь между актерами Akka.Net и ViewModel.
Вы увидите, что ViewModel вызывает ActorSystem вот так
private void StartCpuMethod() {
Debug.WriteLine("StartCpuMethod");
ActorSystemReference.Start();
}
private void StopCpuMethod() {
Debug.WriteLine("StopCpuMethod");
ActorSystemReference.Stop();
}
с актером, получающим эти сообщения
public CPUReadActor()
{
Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage());
Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg));
}
private void ReceiveSyncMessage(ReadCPUSyncMessage msg)
{
switch (msg.Op)
{
case SyncOp.Start:
OnCommandStart();
break;
case SyncOp.Stop:
OnCommandStop();
break;
default:
throw new Exception("unknown Op " + msg.Op.ToString());
}
}
и наоборот от актера
public ChartingActor(Action<float, DateTime> dataPointSetter)
{
this._dataPointSetter = dataPointSetter;
Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg));
}
private void ReceiveDrawPointMessage(DrawPointMessage msg)
{
_dataPointSetter(msg.Value, msg.Date);
}
в ViewModel
public MainWindowViewModel()
{
StartCpuCommand = new RelayCommand(StartCpuMethod);
StopCpuCommand = new RelayCommand(StopCpuMethod);
SetupChartModel();
Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d));
ActorSystemReference.CreateActorSystem(dataPointSetter);
}
private void SetDataPoint(float value, DateTime date)
{
CurrentValue = value;
UpdateLineSeries(value, date);
}
На мой взгляд, лучше всего использовать async/await. https://msdn.microsoft.com/ru-ru/library/mt674882.aspx
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad);
}
public ICommand LoadCommand { get; }
private async void OnLoadAync()
{
await SomethingAwaitable();
}
private Task<bool> SomethingAwaitable()
{
//Your code
}
}