diff --git a/Terminal.Gui/ViewBase/View.Command.cs b/Terminal.Gui/ViewBase/View.Command.cs index ca8de67a1..0c7a87948 100644 --- a/Terminal.Gui/ViewBase/View.Command.cs +++ b/Terminal.Gui/ViewBase/View.Command.cs @@ -141,6 +141,13 @@ public partial class View // Command APIs Accepting?.Invoke (this, args); } + // If Accepting was handled, raise Accepted (non-cancelable event) + if (args.Handled) + { + Logging.Debug ($"{Title} ({ctx?.Source?.Title}) - Calling RaiseAccepted"); + RaiseAccepted (ctx); + } + // Accept is a special case where if the event is not canceled, the event is // - Invoked on any peer-View with IsDefault == true // - bubbled up the SuperView hierarchy. @@ -201,6 +208,48 @@ public partial class View // Command APIs /// public event EventHandler? Accepting; + /// + /// Raises the / event indicating the View has been accepted. + /// This is called after has been raised and not cancelled. + /// + /// + /// + /// Unlike , this event cannot be cancelled. It is raised after the View has been accepted. + /// + /// + /// The command context. + protected void RaiseAccepted (ICommandContext? ctx) + { + CommandEventArgs args = new () { Context = ctx }; + + OnAccepted (args); + Accepted?.Invoke (this, args); + } + + /// + /// Called when the View has been accepted. This is called after has been raised and not cancelled. + /// + /// + /// + /// Unlike , this method is called after the View has been accepted and cannot cancel the operation. + /// + /// + /// The event arguments. + protected virtual void OnAccepted (CommandEventArgs args) { } + + /// + /// Event raised when the View has been accepted. This is raised after has been raised and not cancelled. + /// + /// + /// + /// Unlike , this event cannot be cancelled. It is raised after the View has been accepted. + /// + /// + /// See for more information. + /// + /// + public event EventHandler? Accepted; + /// /// Called when the user has performed an action (e.g. ) causing the View to change state. /// Calls which can be cancelled; if not cancelled raises . diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index b9efaa91f..4963dee20 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -136,40 +136,7 @@ public class Menu : Bar return false; } - // TODO: Consider moving Accepted to Bar? - /// - /// Raises the / event indicating an item in this menu (or submenu) - /// was accepted. This is used to determine when to hide the menu. - /// - /// - /// - protected void RaiseAccepted (ICommandContext? ctx) - { - //Logging.Trace ($"RaiseAccepted: {ctx}"); - CommandEventArgs args = new () { Context = ctx }; - - OnAccepted (args); - Accepted?.Invoke (this, args); - } - - /// - /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu. - /// - /// - /// - /// - protected virtual void OnAccepted (CommandEventArgs args) { } - - /// - /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu. - /// - /// - /// - /// See for more information. - /// - /// - public event EventHandler? Accepted; /// protected override void OnFocusedChanged (View? previousFocused, View? focused) diff --git a/Terminal.Gui/Views/Menu/MenuItem.cs b/Terminal.Gui/Views/Menu/MenuItem.cs index 01ca14922..e11d6235b 100644 --- a/Terminal.Gui/Views/Menu/MenuItem.cs +++ b/Terminal.Gui/Views/Menu/MenuItem.cs @@ -143,15 +143,10 @@ public class MenuItem : Shortcut { // Logging.Debug ($"{Title} - calling base.DispatchCommand..."); // Base will Raise Selected, then Accepting, then invoke the Action, if any + // Note: base.DispatchCommand will call RaiseAccepted via RaiseAccepting when handled ret = base.DispatchCommand (commandContext); } - if (ret is true) - { - // Logging.Debug ($"{Title} - Calling RaiseAccepted"); - RaiseAccepted (commandContext); - } - return ret; } @@ -205,42 +200,7 @@ public class MenuItem : Shortcut return base.OnMouseEnter (eventArgs); } - // TODO: Consider moving Accepted to Shortcut? - /// - /// Raises the / event indicating this item (or submenu) - /// was accepted. This is used to determine when to hide the menu. - /// - /// - /// - protected void RaiseAccepted (ICommandContext? ctx) - { - //Logging.Trace ($"RaiseAccepted: {ctx}"); - CommandEventArgs args = new () { Context = ctx }; - - OnAccepted (args); - Accepted?.Invoke (this, args); - } - - /// - /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the - /// menu. - /// - /// - /// - /// - protected virtual void OnAccepted (CommandEventArgs args) { } - - /// - /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the - /// menu. - /// - /// - /// - /// See for more information. - /// - /// - public event EventHandler? Accepted; /// protected override void Dispose (bool disposing) diff --git a/Terminal.Gui/Views/Menu/PopoverMenu.cs b/Terminal.Gui/Views/Menu/PopoverMenu.cs index ef0767218..7f1e45979 100644 --- a/Terminal.Gui/Views/Menu/PopoverMenu.cs +++ b/Terminal.Gui/Views/Menu/PopoverMenu.cs @@ -560,40 +560,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable return false; } - /// - /// Raises the / event indicating a menu (or submenu) - /// was accepted and the Menus in the PopoverMenu were hidden. Use this to determine when to hide the PopoverMenu. - /// - /// - /// - protected void RaiseAccepted (ICommandContext? ctx) - { - // Logging.Debug ($"{Title} - RaiseAccepted: {ctx}"); - CommandEventArgs args = new () { Context = ctx }; - OnAccepted (args); - Accepted?.Invoke (this, args); - } - - /// - /// Called when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the - /// menu. - /// - /// - /// - /// - protected virtual void OnAccepted (CommandEventArgs args) { } - - /// - /// Raised when the user has accepted an item in this menu (or submenu. This is used to determine when to hide the - /// menu. - /// - /// - /// - /// See for more information. - /// - /// - public event EventHandler? Accepted; private void MenuOnSelectedMenuItemChanged (object? sender, MenuItem? e) { diff --git a/Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs b/Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs index f358f58ed..00b9dea4b 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/ViewCommandTests.cs @@ -124,9 +124,124 @@ public class ViewCommandTests Assert.Equal (0, view.OnAcceptedCount); } - #endregion OnAccept/Accept tests + #region Accepted tests + + [Fact] + public void Accepted_Event_Is_Raised_After_Accepting_When_Handled () + { + View view = new (); + var acceptingInvoked = false; + var acceptedInvoked = false; + + view.Accepting += (sender, e) => + { + acceptingInvoked = true; + e.Handled = true; + }; + + view.Accepted += (sender, e) => + { + acceptedInvoked = true; + Assert.True (acceptingInvoked); // Accepting should be raised first + }; + + bool? ret = view.InvokeCommand (Command.Accept); + Assert.True (ret); + Assert.True (acceptingInvoked); + Assert.True (acceptedInvoked); + } + + [Fact] + public void Accepted_Event_Not_Raised_When_Accepting_Not_Handled () + { + View view = new (); + var acceptingInvoked = false; + var acceptedInvoked = false; + + view.Accepting += (sender, e) => + { + acceptingInvoked = true; + e.Handled = false; + }; + + view.Accepted += (sender, e) => + { + acceptedInvoked = true; + }; + + // When not handled, Accept bubbles to SuperView, so returns false (no superview) + bool? ret = view.InvokeCommand (Command.Accept); + Assert.False (ret); + Assert.True (acceptingInvoked); + Assert.False (acceptedInvoked); // Should not be invoked when not handled + } + + [Fact] + public void Accepted_Event_Cannot_Be_Cancelled () + { + View view = new (); + var acceptedInvoked = false; + + view.Accepting += (sender, e) => + { + e.Handled = true; + }; + + view.Accepted += (sender, e) => + { + acceptedInvoked = true; + // Accepted event has Handled property but it doesn't affect flow + e.Handled = false; + }; + + bool? ret = view.InvokeCommand (Command.Accept); + Assert.True (ret); + Assert.True (acceptedInvoked); + } + + [Fact] + public void OnAccepted_Called_When_Accepting_Handled () + { + OnAcceptedTestView view = new (); + + view.Accepting += (sender, e) => + { + e.Handled = true; + }; + + view.InvokeCommand (Command.Accept); + Assert.Equal (1, view.OnAcceptedCallCount); + } + + [Fact] + public void OnAccepted_Not_Called_When_Accepting_Not_Handled () + { + OnAcceptedTestView view = new (); + + view.Accepting += (sender, e) => + { + e.Handled = false; + }; + + view.InvokeCommand (Command.Accept); + Assert.Equal (0, view.OnAcceptedCallCount); + } + + private class OnAcceptedTestView : View + { + public int OnAcceptedCallCount { get; private set; } + + protected override void OnAccepted (CommandEventArgs args) + { + OnAcceptedCallCount++; + base.OnAccepted (args); + } + } + + #endregion Accepted tests + #region OnSelect/Select tests [Theory]