#nullable enable using System.ComponentModel; namespace Terminal.Gui; public partial class View // Command APIs { #region Default Implementation /// /// Helper to configure all things Command related for a View. Called from the View constructor. /// private void SetupCommands () { AddCommand (Command.Accept, RaiseAcceptEvent); AddCommand ( Command.HotKey, () => { SetFocus (); return RaiseHotKeyCommandEvent (); }); AddCommand (Command.Select, RaiseSelectEvent); } /// /// Called when the command is invoked. Raises /// event. /// /// /// If the event was canceled. If the event was raised but not canceled. /// If no event was raised. /// protected bool? RaiseAcceptEvent () { HandledEventArgs args = new (); // Best practice is to invoke the virtual method first. // This allows derived classes to handle the event and potentially cancel it. args.Handled = OnAccept (args) || args.Handled; if (!args.Handled) { // If the event is not canceled by the virtual method, raise the event to notify any external subscribers. Accept?.Invoke (this, args); } // Accept is a special case where if the event is not canceled, the event is bubbled up the SuperView hierarchy. if (!args.Handled) { return SuperView?.InvokeCommand (Command.Accept) == true; } return Accept is null ? null : args.Handled; } /// /// Called when the command is received. Set to /// to stop processing. /// /// /// to stop processing. protected virtual bool OnAccept (HandledEventArgs args) { return false; } /// /// Cancelable event raised when the command is invoked. Set /// /// to cancel the event. /// public event EventHandler? Accept; /// /// Called when the command is invoked. Raises /// event. /// /// /// If the event was canceled. If the event was raised but not canceled. /// If no event was raised. /// protected bool? RaiseSelectEvent () { HandledEventArgs args = new (); // Best practice is to invoke the virtual method first. // This allows derived classes to handle the event and potentially cancel it. if (OnSelect (args) || args.Handled) { return true; } // If the event is not canceled by the virtual method, raise the event to notify any external subscribers. Select?.Invoke (this, args); return Select is null ? null : args.Handled; } /// /// Called when the command is received. Set to /// to stop processing. /// /// /// to stop processing. protected virtual bool OnSelect (HandledEventArgs args) { return false; } /// /// Cancelable event raised when the command is invoked. Set /// /// to cancel the event. /// public event EventHandler? Select; /// /// Called when the command is invoked. Raises /// event. /// /// /// If the event was handled. If the event was raised but not handled. /// If no event was raised. /// protected bool? RaiseHotKeyCommandEvent () { HandledEventArgs args = new (); // Best practice is to invoke the virtual method first. // This allows derived classes to handle the event and potentially cancel it. if (OnHotKeyCommand (args) || args.Handled) { return true; } // If the event is not canceled by the virtual method, raise the event to notify any external subscribers. HotKeyCommand?.Invoke (this, args); return HotKeyCommand is null ? null : args.Handled; } /// /// Called when the command is received. Set to /// to stop processing. /// /// /// to stop processing. protected virtual bool OnHotKeyCommand (HandledEventArgs args) { return false; } /// /// Cancelable event raised when the command is invoked. Set /// /// to cancel the event. /// public event EventHandler? HotKeyCommand; #endregion Default Implementation /// /// /// Sets the function that will be invoked for a . Views should call /// AddCommand for each command they support. /// /// /// If AddCommand has already been called for will /// replace the old one. /// /// /// /// /// This version of AddCommand is for commands that require . Use /// /// in cases where the command does not require a . /// /// /// The command. /// The function. protected void AddCommand (Command command, Func f) { CommandImplementations [command] = f; } /// /// /// Sets the function that will be invoked for a . Views should call /// AddCommand for each command they support. /// /// /// If AddCommand has already been called for will /// replace the old one. /// /// /// /// /// This version of AddCommand is for commands that do not require a . /// If the command requires context, use /// /// /// /// The command. /// The function. protected void AddCommand (Command command, Func f) { CommandImplementations [command] = ctx => f (); } /// Returns all commands that are supported by this . /// public IEnumerable GetSupportedCommands () { return CommandImplementations.Keys; } /// /// Invokes the specified commands. /// /// /// The key that caused the commands to be invoked, if any. /// /// /// if no command was found. /// if the command was invoked the command was handled. /// if the command was invoked and the command was not handled. /// public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null) { bool? toReturn = null; foreach (Command command in commands) { if (!CommandImplementations.ContainsKey (command)) { throw new NotSupportedException (@$"{command} is not supported by ({GetType ().Name})."); } // each command has its own return value bool? thisReturn = InvokeCommand (command, key, keyBinding); // if we haven't got anything yet, the current command result should be used toReturn ??= thisReturn; // if ever see a true then that's what we will return if (thisReturn ?? false) { toReturn = true; } } return toReturn; } /// Invokes the specified command. /// The command to invoke. /// The key that caused the command to be invoked, if any. /// /// /// if no command was found. if the command was invoked, and it /// handled the command. if the command was invoked, and it did not handle the command. /// public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null) { if (CommandImplementations.TryGetValue (command, out Func? implementation)) { var context = new CommandContext (command, key, keyBinding); // Create the context here return implementation (context); } return null; } }