Johannes Wanzek
Johannes Wanzek

Reputation: 2875

CompositeCommand wait for all Child Commands to complete

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?

enter image description here

Upvotes: 2

Views: 1129

Answers (2)

RickNo
RickNo

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

Sriram Sakthivel
Sriram Sakthivel

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

Related Questions