-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added implementation of base view model and async delegate command
- Loading branch information
kirmir
committed
Oct 15, 2016
1 parent
4ab910a
commit ebe541c
Showing
9 changed files
with
485 additions
and
0 deletions.
There are no files selected for viewing
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
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 |
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
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"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Interfaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Locals/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=MethodPropertyEvent/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Other/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Parameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></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> |
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
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); | ||
} | ||
} | ||
} |
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
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(); | ||
} | ||
} | ||
} |
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
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); | ||
} | ||
} | ||
} |
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
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); | ||
} | ||
} |
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
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))); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.