Reputation: 2875
In my Dialog I have a TabControl
with ChildViews using Prism
. For the Save Commands I'm using a CompositeCommand
. Everything works as expected.
The only problem is: I want to wait for every ChildViewModel to complete the async Save process and then close the dialog when everything is done.
Unfortunately CompositeCommands don't support that feature. So how can I wait until every ViewModel finished work before I close the dialog?
Upvotes: 2
Views: 1129
Reputation: 423
I had the same question and after looking at this answer, I came up with an alternative solution (but upvoted the accepted answer), assuming Prism is used as mentioned in the question.
If the commands you need to register and wait for are not asynchronous (regular DelegateCommand), you can simply inherit from CompositeCommand and leverage all the other functionalities it offers (IActiveAware monitoring).
/// <summary>
/// Callback composite command.
/// </summary>
public class CallbackCompositeCommand : CompositeCommand
{
/// <summary>
/// The callback invoked when commands execution completes.
/// </summary>
private readonly Action<object> executedCallback;
/// <summary>
/// Initializes a new instance of the <see cref="CallbackCompositeCommand"/> class.
/// </summary>
/// <param name="executedCallback">
/// The callback that will be invoked upon execution completion.
/// </param>
public CallbackCompositeCommand(Action<object> executedCallback)
: this(executedCallback, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackCompositeCommand"/> class.
/// </summary>
/// <param name="executedCallback">
/// The callback that will be invoked upon execution completion.
/// </param>
/// <param name="monitorCommandActivity">
/// Indicates when the command activity is going to be monitored.
/// </param>
public CallbackCompositeCommand(Action<object> executedCallback, bool monitorCommandActivity)
: base(monitorCommandActivity)
{
this.executedCallback = executedCallback;
}
/// <summary>
/// Forwards <see cref="M:System.Windows.Input.ICommand.Execute(System.Object)" /> to the registered commands.
/// </summary>
/// <param name="parameter">Data used by the command.
/// If the command does not require data to be passed, this object can be set to <see langword="null" />.
/// </param>
public override void Execute(object parameter)
{
base.Execute(parameter);
this.executedCallback(parameter);
}
}
Upvotes: 0
Reputation: 73502
Alright. Using Task based Asynchronous Pattern your requirement is trivial thing to do.
You need some sort of AsynchronousCommand
which we don't have yet in framework. We can create it ourself with little effort.
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
You need composite implementation of this interface which goes as follows
public class AsyncCompositeCommand : IAsyncCommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly IEnumerable<IAsyncCommand> commands;
private readonly Action<object> executedCallback;
public AsyncCompositeCommand(IEnumerable<IAsyncCommand> commands, Action<object> executedCallback)
{
this.commands = commands;
this.executedCallback = executedCallback;
}
public bool CanExecute(object parameter)
{
return commands.Any(x => x.CanExecute(parameter));
}
public async Task ExecuteAsync(object parameter)
{
var pendingTasks = commands.Select(c=> c.ExecuteAsync(parameter))
.ToList();
await Task.WhenAll(pendingTasks);
executedCallback(parameter);//Notify
}
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
}
In above class ExecuteAsync
starts other child commands in parallel. If you prefer it to be sequential, you can replace the ExecuteAsync
method with below implementation.
public async Task ExecuteAsync(object parameter)
{
foreach (var cmd in commands)
{
await cmd.ExecuteAsync(parameter);
}
executedCallback(parameter);//Notify
}
executedCallback
is a delegate which will be called when all of the child commands are done. You can have your window close code wrapped in that delegate.
Your view model would look something like this:
public class ViewModel
{
public ICommand SaveCommand { get; private set; }
public ViewModel()
{
SaveCommand = new AsyncCompositeCommand(new IAsyncCommand[]
{
command1,
command2,
...
},
param => Console.WriteLine("Done"));
}
}
It's up to you how you bind ViewModel.SaveCommand
property in UI.
Upvotes: 3