CompositeCommand 等待所有子命令完成
CompositeCommand wait for all Child Commands to complete
在我的对话框中,我有一个 TabControl
和使用 Prism
的 ChildViews。对于保存命令,我使用 CompositeCommand
。一切都按预期工作。
唯一的问题是:我想等待每个 ChildViewModel 完成异步保存过程,然后在一切完成后关闭对话框。
很遗憾,CompositeCommands 不支持该功能。 那么如何才能等到每个 ViewModel 完成工作后再关闭对话框?
好的。使用基于任务的异步模式,您的要求很简单。
您需要某种 AsynchronousCommand
我们在框架中还没有的东西。我们可以毫不费力地自己创建它。
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
您需要此接口的复合实现,如下所示
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);
}
}
在上面 class ExecuteAsync
并行启动其他子命令。如果您希望它是顺序的,您可以用下面的实现替换 ExecuteAsync
方法。
public async Task ExecuteAsync(object parameter)
{
foreach (var cmd in commands)
{
await cmd.ExecuteAsync(parameter);
}
executedCallback(parameter);//Notify
}
executedCallback
是一个委托,将在所有子命令完成时调用。您可以将 window 关闭代码封装在该委托中。
您的视图模型看起来像这样:
public class ViewModel
{
public ICommand SaveCommand { get; private set; }
public ViewModel()
{
SaveCommand = new AsyncCompositeCommand(new IAsyncCommand[]
{
command1,
command2,
...
},
param => Console.WriteLine("Done"));
}
}
如何绑定 ViewModel.SaveCommand
属性 到 UI.
由你决定
我有同样的问题,在查看这个答案后,我想出了一个替代解决方案(但对接受的答案投了赞成票),假设使用了问题中提到的 Prism。
如果您需要注册和等待的命令不是异步的(常规 DelegateCommand),您可以简单地继承 CompositeCommand 并利用它提供的所有其他功能(IActiveAware 监控)。
/// <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);
}
}
在我的对话框中,我有一个 TabControl
和使用 Prism
的 ChildViews。对于保存命令,我使用 CompositeCommand
。一切都按预期工作。
唯一的问题是:我想等待每个 ChildViewModel 完成异步保存过程,然后在一切完成后关闭对话框。
很遗憾,CompositeCommands 不支持该功能。 那么如何才能等到每个 ViewModel 完成工作后再关闭对话框?
好的。使用基于任务的异步模式,您的要求很简单。
您需要某种 AsynchronousCommand
我们在框架中还没有的东西。我们可以毫不费力地自己创建它。
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
您需要此接口的复合实现,如下所示
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);
}
}
在上面 class ExecuteAsync
并行启动其他子命令。如果您希望它是顺序的,您可以用下面的实现替换 ExecuteAsync
方法。
public async Task ExecuteAsync(object parameter)
{
foreach (var cmd in commands)
{
await cmd.ExecuteAsync(parameter);
}
executedCallback(parameter);//Notify
}
executedCallback
是一个委托,将在所有子命令完成时调用。您可以将 window 关闭代码封装在该委托中。
您的视图模型看起来像这样:
public class ViewModel
{
public ICommand SaveCommand { get; private set; }
public ViewModel()
{
SaveCommand = new AsyncCompositeCommand(new IAsyncCommand[]
{
command1,
command2,
...
},
param => Console.WriteLine("Done"));
}
}
如何绑定 ViewModel.SaveCommand
属性 到 UI.
我有同样的问题,在查看这个答案后,我想出了一个替代解决方案(但对接受的答案投了赞成票),假设使用了问题中提到的 Prism。
如果您需要注册和等待的命令不是异步的(常规 DelegateCommand),您可以简单地继承 CompositeCommand 并利用它提供的所有其他功能(IActiveAware 监控)。
/// <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);
}
}