Files
aistudio-wpf-diagram/AIStudio.Wpf.ADiagram/Commands/CompositeCommand.cs
2021-07-23 09:42:22 +08:00

241 lines
9.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Input;
namespace AIStudio.Wpf.ADiagram.Commands
{
/// <summary>
/// The CompositeCommand composes one or more ICommands.
/// </summary>
public class CompositeCommand : ICommand
{
private readonly List<ICommand> _registeredCommands = new List<ICommand>();
private readonly bool _monitorCommandActivity;
private readonly EventHandler _onRegisteredCommandCanExecuteChangedHandler;
private SynchronizationContext _synchronizationContext;
/// <summary>
/// Initializes a new instance of <see cref="CompositeCommand"/>.
/// </summary>
public CompositeCommand()
{
this._onRegisteredCommandCanExecuteChangedHandler = new EventHandler(this.OnRegisteredCommandCanExecuteChanged);
_synchronizationContext = SynchronizationContext.Current;
}
/// <summary>
/// Initializes a new instance of <see cref="CompositeCommand"/>.
/// </summary>
/// <param name="monitorCommandActivity">Indicates when the command activity is going to be monitored.</param>
public CompositeCommand(bool monitorCommandActivity)
: this()
{
this._monitorCommandActivity = monitorCommandActivity;
}
/// <summary>
/// Adds a command to the collection and signs up for the <see cref="ICommand.CanExecuteChanged"/> event of it.
/// </summary>
/// <remarks>
/// If this command is set to monitor command activity, and <paramref name="command"/>
/// implements the <see cref="IActiveAware"/> interface, this method will subscribe to its
/// <see cref="IActiveAware.IsActiveChanged"/> event.
/// </remarks>
/// <param name="command">The command to register.</param>
public virtual void RegisterCommand(ICommand command)
{
if (command == null) throw new ArgumentNullException(nameof(command));
if (command == this)
{
throw new ArgumentException("CannotRegisterCompositeCommandInItself");
}
lock (this._registeredCommands)
{
if (this._registeredCommands.Contains(command))
{
throw new InvalidOperationException("CannotRegisterSameCommandTwice");
}
this._registeredCommands.Add(command);
}
command.CanExecuteChanged += this._onRegisteredCommandCanExecuteChangedHandler;
this.OnCanExecuteChanged();
if (this._monitorCommandActivity)
{
var activeAwareCommand = command as IActiveAware;
if (activeAwareCommand != null)
{
activeAwareCommand.IsActiveChanged += this.Command_IsActiveChanged;
}
}
}
/// <summary>
/// Removes a command from the collection and removes itself from the <see cref="ICommand.CanExecuteChanged"/> event of it.
/// </summary>
/// <param name="command">The command to unregister.</param>
public virtual void UnregisterCommand(ICommand command)
{
if (command == null) throw new ArgumentNullException(nameof(command));
bool removed;
lock (this._registeredCommands)
{
removed = this._registeredCommands.Remove(command);
}
if (removed)
{
command.CanExecuteChanged -= this._onRegisteredCommandCanExecuteChangedHandler;
this.OnCanExecuteChanged();
if (this._monitorCommandActivity)
{
var activeAwareCommand = command as IActiveAware;
if (activeAwareCommand != null)
{
activeAwareCommand.IsActiveChanged -= this.Command_IsActiveChanged;
}
}
}
}
private void OnRegisteredCommandCanExecuteChanged(object sender, EventArgs e)
{
this.OnCanExecuteChanged();
}
/// <summary>
/// Forwards <see cref="ICommand.CanExecute"/> to the registered commands and returns
/// <see langword="true" /> if all of the commands return <see langword="true" />.
/// </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>
/// <returns><see langword="true" /> if all of the commands return <see langword="true" />; otherwise, <see langword="false" />.</returns>
public virtual bool CanExecute(object parameter)
{
bool hasEnabledCommandsThatShouldBeExecuted = false;
ICommand[] commandList;
lock (this._registeredCommands)
{
commandList = this._registeredCommands.ToArray();
}
foreach (ICommand command in commandList)
{
if (this.ShouldExecute(command))
{
if (!command.CanExecute(parameter))
{
return false;
}
hasEnabledCommandsThatShouldBeExecuted = true;
}
}
return hasEnabledCommandsThatShouldBeExecuted;
}
/// <summary>
/// Occurs when any of the registered commands raise <see cref="ICommand.CanExecuteChanged"/>.
/// </summary>
public virtual event EventHandler CanExecuteChanged;
/// <summary>
/// Forwards <see cref="ICommand.Execute"/> 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 virtual void Execute(object parameter)
{
Queue<ICommand> commands;
lock (this._registeredCommands)
{
commands = new Queue<ICommand>(this._registeredCommands.Where(this.ShouldExecute).ToList());
}
while (commands.Count > 0)
{
ICommand command = commands.Dequeue();
command.Execute(parameter);
}
}
/// <summary>
/// Evaluates if a command should execute.
/// </summary>
/// <param name="command">The command to evaluate.</param>
/// <returns>A <see cref="bool"/> value indicating whether the command should be used
/// when evaluating <see cref="CompositeCommand.CanExecute"/> and <see cref="CompositeCommand.Execute"/>.</returns>
/// <remarks>
/// If this command is set to monitor command activity, and <paramref name="command"/>
/// implements the <see cref="IActiveAware"/> interface,
/// this method will return <see langword="false" /> if the command's <see cref="IActiveAware.IsActive"/>
/// property is <see langword="false" />; otherwise it always returns <see langword="true" />.</remarks>
protected virtual bool ShouldExecute(ICommand command)
{
var activeAwareCommand = command as IActiveAware;
if (this._monitorCommandActivity && activeAwareCommand != null)
{
return activeAwareCommand.IsActive;
}
return true;
}
/// <summary>
/// Gets the list of all the registered commands.
/// </summary>
/// <value>A list of registered commands.</value>
/// <remarks>This returns a copy of the commands subscribed to the CompositeCommand.</remarks>
public IList<ICommand> RegisteredCommands
{
get
{
IList<ICommand> commandList;
lock (this._registeredCommands)
{
commandList = this._registeredCommands.ToList();
}
return commandList;
}
}
/// <summary>
/// Raises <see cref="ICommand.CanExecuteChanged"/> on the UI thread so every
/// command invoker can requery <see cref="ICommand.CanExecute"/> to check if the
/// <see cref="CompositeCommand"/> can execute.
/// </summary>a
protected virtual void OnCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
_synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null);
else
handler.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Handler for IsActiveChanged events of registered commands.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">EventArgs to pass to the event.</param>
private void Command_IsActiveChanged(object sender, EventArgs e)
{
this.OnCanExecuteChanged();
}
}
}