Skip to content

Instantly share code, notes, and snippets.

@robibobi

robibobi/AsyncCommand.cs

Last active Nov 30, 2016
Embed
What would you like to do?
Blog "Asynchrone Command Pattern im MVVM-Stil" auf http://thecolorofcode.com/
class AsyncCommand<TParam> : AsyncCommandBase
{
readonly Func<CancellationToken, TParam, Task> _command;
readonly Func<TParam, bool> _canExecuteFunc;
readonly CancelAsyncCommand _cancelCommand;
public AsyncCommand(Func<CancellationToken, TParam, Task> command)
{
_command = command;
_canExecuteFunc = _ => true;
_cancelCommand = new CancelAsyncCommand();
}
public AsyncCommand(Func<CancellationToken, TParam, Task> command, Func<TParam, bool> canExecute)
{
_command = command;
_canExecuteFunc = canExecute;
_cancelCommand = new CancelAsyncCommand();
}
public override bool CanExecute(object parameter)
{
return _canExecuteFunc(SaveCast(parameter)) && (Execution == null || Execution.IsCompleted);
}
public override async Task ExecuteAsync(object parameter)
{
TParam param = SaveCast(parameter);
_cancelCommand.NotifyCommandStarting();
Execution = new NotifyTaskCompletion(_command(_cancelCommand.Token, param));
OnPropertyChanged(nameof(Execution));
RaiseCanExecuteChanged();
await Execution.TaskCompletion;
_cancelCommand.NotifyCommandFinished();
RaiseCanExecuteChanged();
}
public ICommand CancelCommand => _cancelCommand;
public NotifyTaskCompletion Execution { get; private set; }
private TParam SaveCast(object param)
{
if(param == null)
return default(TParam);
return (TParam)param;
}
}
class MainViewModel : ViewModelBase
{
public AsyncCommand<string> CalculatePrimesCmd { get; }
public List<int> Primes { get; set; }
public MainViewModel()
{
CalculatePrimesCmd = new AsyncCommand<string>(OnCalculatePrimes, CanExecuteCalculation);
}
private bool CanExecuteCalculation(string countString)
{
return !String.IsNullOrEmpty(countString) && Regex.IsMatch(countString, @"^\d+$");
}
private Task OnCalculatePrimes(CancellationToken t, string countString)
{
return Task.Factory.StartNew(() =>
{
int count = Convert.ToInt32(countString);
Primes = GeneratePrimesNaive(t, count)
.Take(1000)
.ToList();
RaisePropertyChanged(nameof(Primes));
});
}
private static List<int> GeneratePrimesNaive(CancellationToken t, int n)
{
List<int> primes = new List<int>();
primes.Add(2);
int nextPrime = 3;
while (primes.Count < n && !t.IsCancellationRequested)
{
int sqrt = (int)Math.Sqrt(nextPrime);
bool isPrime = true;
for (int i = 0; (int)primes[i] <= sqrt; i++)
{
if (nextPrime % primes[i] == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
{
primes.Add(nextPrime);
}
nextPrime += 2;
}
return primes;
}
}
<Window x:Class="Tcoc.Blog.Async.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Tcoc.Blog.Async"
Title="Primes Calculator"
Width="525"
Height="350">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid Margin="50">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="16" />
<RowDefinition Height="30" />
<RowDefinition Height="16" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" Text="Enter prime count" />
<!-- Eingabefeld Anzahl -->
<TextBox x:Name="PrimesCountTB" Grid.Column="1" />
<!-- Primzahlen berechnen -->
<Button Grid.Row="2"
Grid.Column="0"
Command="{Binding CalculatePrimesCmd, Mode=OneWay}"
CommandParameter="{Binding ElementName=PrimesCountTB, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Content="Calculate Primes" />
<!-- Berechnung abbrechen -->
<Button Grid.Row="2"
Grid.Column="1"
Command="{Binding CalculatePrimesCmd.CancelCommand}"
Content="Cancel" />
<!-- Ergebnis Anzeige -->
<ListView Grid.Row="4"
Grid.ColumnSpan="2"
ItemsSource="{Binding Primes, IsAsync=True}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="400" ItemWidth="120" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Window>
class NotifyTaskCompletion : INotifyPropertyChanged
{
public NotifyTaskCompletion(Task task)
{
Task = task;
TaskCompletion = WatchTaskAsync(task);
}
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this, new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
public Task Task { get; }
public Task TaskCompletion { get; }
public TaskStatus Status => Task.Status;
public bool IsCompleted => Task.IsCompleted;
public bool IsNotCompleted => !Task.IsCompleted;
public bool IsSuccessfullyCompleted => Task.Status == TaskStatus.RanToCompletion;
public bool IsCanceled => Task.IsCanceled;
public bool IsFaulted => Task.IsFaulted;
public AggregateException Exception => Task.Exception;
public Exception InnerException => (Exception == null) ? null : Exception.InnerException;
public string ErrorMessage => InnerException == null ? null : InnerException.Message;
public event PropertyChangedEventHandler PropertyChanged;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment