Created
July 26, 2016 19:06
-
-
Save Athari/201775c28e2d549a0c01b936e82de532 to your computer and use it in GitHub Desktop.
AwaitableCommandExample by Vlad@RuSO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Windows; | |
using System.Windows.Input; | |
namespace AwaitableCommandExample | |
{ | |
public partial class App | |
{ | |
protected override async void OnStartup(StartupEventArgs e) | |
{ | |
base.OnStartup(e); | |
using (var appFlowCancellation = new CancellationTokenSource()) | |
{ | |
CommandManager.RegisterClassCommandBinding(typeof(Window), | |
new CommandBinding(ApplicationCommands.Close, (o, args) => appFlowCancellation.Cancel())); | |
var mainVM = new MainVM(); | |
var mainWindow = new MainWindow() { DataContext = mainVM }; | |
mainWindow.Show(); | |
try | |
{ | |
await mainVM.Run(appFlowCancellation.Token); | |
} | |
catch (OperationCanceledException) when (appFlowCancellation.IsCancellationRequested) | |
{ } | |
mainWindow.Close(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading.Tasks; | |
using System.Windows.Input; | |
namespace AwaitableCommandExample | |
{ | |
class AwaitableCommand : VM, ICommand | |
{ | |
readonly TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); | |
public Task Task => tcs.Task; | |
public event EventHandler CanExecuteChanged; | |
bool canExecute = true; | |
public bool CanExecute | |
{ | |
get { return canExecute; } | |
set { if (canExecute != value) { canExecute = value; Raise(); } } | |
} | |
void Raise() | |
{ | |
CanExecuteChanged?.Invoke(this, EventArgs.Empty); | |
RaisePropertyChanged(nameof(CanExecute)); | |
} | |
bool ICommand.CanExecute(object parameter) | |
{ | |
return CanExecute; | |
} | |
void ICommand.Execute(object parameter) | |
{ | |
if (!CanExecute) | |
return; | |
tcs.TrySetResult(0); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AwaitableCommandExample | |
{ | |
class ExecutionVM : VM | |
{ | |
public ExecutionVM(ParameterVM parameterVM) | |
{ | |
ParameterVM = parameterVM; | |
} | |
public ParameterVM ParameterVM { get; } | |
public AwaitableCommand Finish { get; } = new AwaitableCommand(); | |
public AwaitableCommand Cancel { get; } = new AwaitableCommand(); | |
string status; | |
public string Status | |
{ | |
get { return status; } | |
set { if (status != value) { status = value; RaisePropertyChanged(); } } | |
} | |
public async Task Run(CancellationToken ct) | |
{ | |
Finish.CanExecute = false; | |
var breakCts = new CancellationTokenSource(); | |
try | |
{ | |
using (var commonCt = CancellationTokenSource.CreateLinkedTokenSource(ct, breakCts.Token)) | |
{ | |
var workerTask = WorkerTask(commonCt.Token, new Progress<string>(s => Status = s)); | |
var winner = await Task.WhenAny(workerTask, Cancel.Task); | |
if (winner == Cancel.Task) | |
breakCts.Cancel(); | |
await workerTask; | |
} | |
} | |
catch (OperationCanceledException) when (breakCts.IsCancellationRequested) | |
{ } | |
Finish.CanExecute = true; | |
Cancel.CanExecute = false; | |
using (var cancelTask = ct.ThrowWhenCanceled()) | |
await await Task.WhenAny(Finish.Task, cancelTask.ToTask()); | |
} | |
async Task WorkerTask(CancellationToken ct, IProgress<string> statusProgress) | |
{ | |
var from = ParameterVM.FromPath; | |
var to = ParameterVM.ToPath; | |
// here is call to model | |
for (double progress = 0; progress < 1.0; progress = Math.Min(progress + 0.33333333, 1.0)) | |
{ | |
statusProgress.Report($"Copying {from} to {to} ({progress:P} ready)"); | |
await Task.Delay(1000, ct); | |
} | |
statusProgress.Report($"Copying {from} to {to} (100% ready)"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AwaitableCommandExample | |
{ | |
class MainVM : VM | |
{ | |
readonly ParameterVM parameterVM = new ParameterVM(); | |
readonly ParameterEditorVM parameterInputVM; | |
readonly ExecutionVM executionVM; | |
public MainVM() | |
{ | |
parameterInputVM = new ParameterEditorVM(parameterVM); | |
executionVM = new ExecutionVM(parameterVM); | |
} | |
public async Task Run(CancellationToken ct) | |
{ | |
CurrentVM = parameterInputVM; | |
var inputResult = await parameterInputVM.Run(ct); | |
if (!inputResult) | |
return; | |
CurrentVM = executionVM; | |
await executionVM.Run(ct); | |
CurrentVM = null; | |
} | |
VM currentVM; | |
public VM CurrentVM | |
{ | |
get { return currentVM; } | |
set { if (currentVM != value) { currentVM = value; RaisePropertyChanged(); } } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window x:Class="AwaitableCommandExample.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:local="clr-namespace:AwaitableCommandExample" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
mc:Ignorable="d" | |
Title="MainWindow" Height="350" Width="525"> | |
<Window.Resources> | |
<DataTemplate DataType="{x:Type local:ParameterEditorVM}"> | |
<Grid d:DataContext="{d:DesignInstance Type=local:ParameterEditorVM, IsDesignTimeCreatable=True}"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="*"/> | |
<RowDefinition Height="Auto"/> | |
</Grid.RowDefinitions> | |
<DockPanel LastChildFill="True" Grid.Row="0"> | |
<Label DockPanel.Dock="Left" Target="{Binding ElementName=From}">Enter source path:</Label> | |
<TextBox Name="From" Text="{Binding ParameterVM.FromPath}"/> | |
</DockPanel> | |
<DockPanel LastChildFill="True" Grid.Row="1"> | |
<Label DockPanel.Dock="Left" Target="{Binding ElementName=To}">Enter destination path:</Label> | |
<TextBox Name="To" Text="{Binding ParameterVM.ToPath}"/> | |
</DockPanel> | |
<StackPanel Orientation="Horizontal" Grid.Row="3" HorizontalAlignment="Right"> | |
<Button Command="{Binding Cancel}" MinWidth="75" Margin="10,10,0,10">Cancel</Button> | |
<Button Command="{Binding Finish}" MinWidth="75" Margin="10">Next</Button> | |
</StackPanel> | |
</Grid> | |
</DataTemplate> | |
<DataTemplate DataType="{x:Type local:ExecutionVM}"> | |
<Grid d:DataContext="{d:DesignInstance Type=local:ExecutionVM, IsDesignTimeCreatable=True}"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="*"/> | |
<RowDefinition Height="Auto"/> | |
</Grid.RowDefinitions> | |
<TextBlock Grid.Row="0" Text="{Binding Status}"/> | |
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right"> | |
<Button Command="{Binding Cancel}" MinWidth="75" Margin="10,10,0,10">Cancel</Button> | |
<Button Command="{Binding Finish}" MinWidth="75" Margin="10">Next</Button> | |
</StackPanel> | |
</Grid> | |
</DataTemplate> | |
</Window.Resources> | |
<Grid> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="*"/> | |
</Grid.RowDefinitions> | |
<Menu Grid.Row="0"> | |
<MenuItem Header="File"> | |
<MenuItem Header="Exit" Command="Close"/> | |
</MenuItem> | |
</Menu> | |
<ContentPresenter Grid.Row="1" Content="{Binding CurrentVM}"/> | |
</Grid> | |
</Window> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AwaitableCommandExample | |
{ | |
class ParameterEditorVM : VM | |
{ | |
public ParameterEditorVM(ParameterVM parameterVM) | |
{ | |
ParameterVM = parameterVM; | |
} | |
public ParameterVM ParameterVM { get; } | |
public AwaitableCommand Finish { get; } = new AwaitableCommand(); | |
public AwaitableCommand Cancel { get; } = new AwaitableCommand(); | |
public async Task<bool> Run(CancellationToken ct) | |
{ | |
using (var cancelRegistration = ct.ThrowWhenCanceled()) | |
{ | |
var winner = await Task.WhenAny(Finish.Task, Cancel.Task, cancelRegistration.ToTask()); | |
await winner; | |
return winner == Finish.Task; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AwaitableCommandExample | |
{ | |
class ParameterVM : VM | |
{ | |
string fromPath = @"from\file", toPath = @"to\file"; | |
public string FromPath | |
{ | |
get { return fromPath; } | |
set { if (fromPath != value) { fromPath = value; RaisePropertyChanged(); } } | |
} | |
public string ToPath | |
{ | |
get { return toPath; } | |
set { if (toPath != value) { toPath = value; RaisePropertyChanged(); } } | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AwaitableCommandExample | |
{ | |
static class Tools | |
{ | |
public class CancellationDisposableAwaitable : IDisposable | |
{ | |
public CancellationDisposableAwaitable(CancellationToken cancellationToken) | |
{ | |
registration = cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs); | |
} | |
public async Task ToTask() => await this; | |
public TaskAwaiter<bool> GetAwaiter() => tcs.Task.GetAwaiter(); | |
public void Dispose() => registration.Dispose(); | |
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); | |
CancellationTokenRegistration registration; | |
} | |
public class ThrowingCancellationDisposableAwaitable : IDisposable | |
{ | |
public ThrowingCancellationDisposableAwaitable(CancellationToken cancellationToken) | |
{ | |
registration = cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetCanceled(), tcs); | |
} | |
public async Task ToTask() => await this; | |
public TaskAwaiter<bool> GetAwaiter() => tcs.Task.GetAwaiter(); | |
public void Dispose() => registration.Dispose(); | |
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); | |
CancellationTokenRegistration registration; | |
} | |
public static CancellationDisposableAwaitable WhenCanceled(this CancellationToken cancellationToken) | |
{ | |
return new CancellationDisposableAwaitable(cancellationToken); | |
} | |
public static ThrowingCancellationDisposableAwaitable ThrowWhenCanceled(this CancellationToken cancellationToken) | |
{ | |
return new ThrowingCancellationDisposableAwaitable(cancellationToken); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.ComponentModel; | |
using System.Runtime.CompilerServices; | |
namespace AwaitableCommandExample | |
{ | |
class VM : INotifyPropertyChanged | |
{ | |
public event PropertyChangedEventHandler PropertyChanged; | |
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) => | |
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment