Skip to content

Commit

Permalink
Added implementation of base view model and async delegate command
Browse files Browse the repository at this point in the history
  • Loading branch information
kirmir committed Oct 15, 2016
1 parent 4ab910a commit ebe541c
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 0 deletions.
22 changes: 22 additions & 0 deletions WpfAsyncPack.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfAsyncPack", "WpfAsyncPack\WpfAsyncPack.csproj", "{E60905BC-9858-489C-8F14-CD68E9D96F04}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E60905BC-9858-489C-8F14-CD68E9D96F04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E60905BC-9858-489C-8F14-CD68E9D96F04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E60905BC-9858-489C-8F14-CD68E9D96F04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E60905BC-9858-489C-8F14-CD68E9D96F04}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
71 changes: 71 additions & 0 deletions WpfAsyncPack.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BETWEEN_ATTRIBUTE_SECTIONS/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">140</s:Int64>

<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Interfaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Locals/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=MethodPropertyEvent/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Other/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Parameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>




<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_LINQ_QUERY/@EntryValue">True</s:Boolean>

<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARRAY_AND_OBJECT_INITIALIZER/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXPRESSION/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_FOR_STMT/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PARAMETER/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTIPLE_DECLARATION/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_CONSTRAINS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean>



<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_SINGLE_LINE_COMMENT/@EntryValue">1</s:Int64>





<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_ANONYMOUS_METHOD_BLOCK/@EntryValue">True</s:Boolean>


<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>

<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>


<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean>


<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/STICK_COMMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>






</wpf:ResourceDictionary>
135 changes: 135 additions & 0 deletions WpfAsyncPack/AsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfAsyncPack
{
public class AsyncCommand<TResult> : AsyncCommandBase, INotifyPropertyChanged
{
private readonly Func<CancellationToken, Task<TResult>> _command;
private readonly CancelAsyncCommand _cancelCommand = new CancelAsyncCommand();

private NotifyTaskCompletion<TResult> _execution;

public event PropertyChangedEventHandler PropertyChanged;

public AsyncCommand(Func<CancellationToken, Task<TResult>> command)
{
_command = command;
}

public ICommand CancelCommand => _cancelCommand;

public NotifyTaskCompletion<TResult> Execution
{
get { return _execution; }
private set
{
_execution = value;
RaisePropertyChanged();
}
}

public override async Task ExecuteAsync(object parameter)
{
_cancelCommand.NotifyCommandStarting();
Execution = new NotifyTaskCompletion<TResult>(_command(_cancelCommand.Token));
RaiseCanExecuteChanged();
await Execution.TaskCompletion;
_cancelCommand.NotifyCommandFinished();
RaiseCanExecuteChanged();
}

public override bool CanExecute(object parameter)
{
return Execution == null || Execution.IsCompleted;
}

protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private sealed class CancelAsyncCommand : ICommand
{
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _commandExecuting;

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public CancellationToken Token => _cancellationTokenSource.Token;

void ICommand.Execute(object parameter)
{
_cancellationTokenSource.Cancel();
RaiseCanExecuteChanged();
}

bool ICommand.CanExecute(object parameter)
{
return _commandExecuting && !_cancellationTokenSource.IsCancellationRequested;
}

public void NotifyCommandStarting()
{
_commandExecuting = true;
if (_cancellationTokenSource.IsCancellationRequested)
{
_cancellationTokenSource = new CancellationTokenSource();
RaiseCanExecuteChanged();
}
}

public void NotifyCommandFinished()
{
_commandExecuting = false;
RaiseCanExecuteChanged();
}

private static void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}

public static class AsyncCommand
{
public static AsyncCommand<object> Create(Func<Task> command)
{
return new AsyncCommand<object>(
async token =>
{
await command();
return null;
});
}

public static AsyncCommand<TResult> Create<TResult>(Func<Task<TResult>> command)
{
return new AsyncCommand<TResult>(token => command());
}

public static AsyncCommand<object> Create(Func<CancellationToken, Task> command)
{
return new AsyncCommand<object>(
async token =>
{
await command(token);
return null;
});
}

public static AsyncCommand<TResult> Create<TResult>(Func<CancellationToken, Task<TResult>> command)
{
return new AsyncCommand<TResult>(command);
}
}
}
29 changes: 29 additions & 0 deletions WpfAsyncPack/AsyncCommandBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfAsyncPack
{
public abstract class AsyncCommandBase : IAsyncCommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}

public abstract Task ExecuteAsync(object parameter);

public abstract bool CanExecute(object parameter);

protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}
37 changes: 37 additions & 0 deletions WpfAsyncPack/BaseViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Threading;

namespace WpfAsyncPack
{
internal abstract class BaseViewModel : INotifyPropertyChanged
{
private readonly Dispatcher _dispatcher = Application.Current.Dispatcher;

public event PropertyChangedEventHandler PropertyChanged;

protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}

storage = value;

// ReSharper disable once ExplicitCallerInfoArgument
RaisePropertyChangedAsync(propertyName);

return true;
}

protected virtual async void RaisePropertyChangedAsync([CallerMemberName] string propertyName = null)
{
// Run in UI thread.
await _dispatcher.InvokeAsync(
() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)),
DispatcherPriority.Normal);
}
}
}
10 changes: 10 additions & 0 deletions WpfAsyncPack/IAsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfAsyncPack
{
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
}
84 changes: 84 additions & 0 deletions WpfAsyncPack/NotifyTaskCompletion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;

namespace WpfAsyncPack
{
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public NotifyTaskCompletion(Task<TResult> task)
{
Task = task;

if (!task.IsCompleted)
{
TaskCompletion = WatchTaskAsync(task);
}
}

public Task<TResult> Task { get; }

public Task TaskCompletion { get; }

public TResult Result => Task.Status == TaskStatus.RanToCompletion ? Task.Result : default(TResult);

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?.InnerException;

public string ErrorMessage => InnerException?.Message;

private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
// Skipped because we handle failures below.
}

var propertyChanged = PropertyChanged;
if (propertyChanged == null)
{
return;
}

propertyChanged(this, new PropertyChangedEventArgs(nameof(Status)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(IsCompleted)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(IsNotCompleted)));

if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs(nameof(IsCanceled)));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs(nameof(IsFaulted)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(Exception)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(InnerException)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(ErrorMessage)));
}
else
{
propertyChanged(this, new PropertyChangedEventArgs(nameof(IsSuccessfullyCompleted)));
propertyChanged(this, new PropertyChangedEventArgs(nameof(Result)));
}
}
}
}
Loading

0 comments on commit ebe541c

Please sign in to comment.