-
-
Notifications
You must be signed in to change notification settings - Fork 38
Open
Description
I have done a bit of work relating to AsyncCommand which I am hoping the community can benefit from.
I had the following objectives and approaches:
- To provide built-in support for cancellation, i.e. a bindable Cancel command is provided automatically as part of the AsyncCommand, if a CancellationToken is accepted by the execution delegate.
- To provide built-in support for progress notification. This was accomplished using the bindable ProgressReport class referenced in my other post.
- To allow a variety of execution delegates -- whether async or sync, and allowing any combination of CancellationToken, ProgressReport, or command parameter object as an input to the execution delegate. This was achieved by having a number of constructors which take the various possible execution delegates and convert them privately into a unified form.
- To be able to execute the delegate either on the current thread (await execution(...)) or on a different thread (await Task.Run(()=>execution(...))). The latter is suitable for executing CPU-bound synchronous tasks in the background. It is captured by a boolean property, RunOnDifferentThread.
- To move away from the CanExecute paradigm toward something more up-to-date, i.e. consistent with property notifications. This was accomplished by allowing the user to bind a boolean function (updated by property notifications) with the meaning that the command can execute only if the function is true. Naturally the method in AsyncCommand to set this function is called CanExecuteOnlyIf( boundFunction). What this does is move us from an async paradigm that requires a lot of special events and handlers to an approach based on binding.
- Note: The solution to this problem (5) involved creating a BoundFunction class which defines a function and binds properties to the inputs of the function. When a bound input is updated then the BoundFunction recalculates and provides a PropertyChanged notification for the result. This is a general-purpose class which is useful, for example, in view-models to combine a number of bindable inputs into a bindable output. (The old way of doing this was to use a MultiBinding, which in addition to being awkward also resulted in moving view-model concepts into the view). To support the type-safe binding of properties in code and to have the compiler check the names of property paths, an additional method Property.Path is needed to extract property paths from lambda expressions.
- To have a simple way of ensuring that by default a command cannot be executed if it is already executing. This was achieved by creating a boolean property CanExecuteWhileExecuting. An extension of this is to have a way to make a set of commands mutually exclusive, which is captured by calling a method, AreMutuallyExclusive, and passing in the list of mutually exclusive commands. A typical use would be to make, for example, FileNew, FileSave, FileOpen all mutually exclusive.
- While the resulting class is somewhat complex, it also provides a high level of functionality. The public footprint is not excessive:
public bool CanExecuteWhileExecuting { get; set; }
public static void AreMutuallyExclusive( IEnumerable commands)
public void CanExecuteOnlyIf(BoundFunction boundFunction)
public void CanExecuteOnlyIf( Tsrc source, Expression<Func<Tsrc,bool>> propertyPath)
public NotifyTask Execution
public bool IsExecuting { get; }
override public async void Execute(object parameter = null)
public async Task ExecuteAsync(object parameter)
public bool RunOnDifferentThread { get; set; }
public ProgressReport ProgressReport { get; }
public ICommand CancelCommand { get; }
I am providing source code that accomplishes all this, and I am hereby entering it into the public domain for anyone to use without restriction. Please also refer to my other post discussing NotifyTask and ProgressReport.
sjb
AsyncCommand.txt
BoundFunction.txt
BoundAction.txt
Property.txt
Metadata
Metadata
Assignees
Labels
No labels