From 4e1d62b8f5d3b8ba01ec258029c84fd7148db470 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:10:32 +0000
Subject: [PATCH] =?UTF-8?q?Simplify=20IRunnable:=20Rename=20Activating?=
=?UTF-8?q?=E2=86=92Starting,=20remove=20Toplevel=20complexity?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: tig <585482+tig@users.noreply.github.com>
---
Terminal.Gui/App/IRunnable.cs | 92 ++------
Terminal.Gui/App/RunnableEventArgs.cs | 88 +-------
Terminal.Gui/ViewBase/Runnable.cs | 236 +++++++-------------
Terminal.Gui/Views/Dialog.cs | 58 +----
Terminal.Gui/Views/Toplevel.cs | 277 +-----------------------
Tests/UnitTests/Views/IRunnableTests.cs | 164 --------------
6 files changed, 111 insertions(+), 804 deletions(-)
delete mode 100644 Tests/UnitTests/Views/IRunnableTests.cs
diff --git a/Terminal.Gui/App/IRunnable.cs b/Terminal.Gui/App/IRunnable.cs
index 3c299cd82..9d97674c8 100644
--- a/Terminal.Gui/App/IRunnable.cs
+++ b/Terminal.Gui/App/IRunnable.cs
@@ -17,7 +17,7 @@ namespace Terminal.Gui.App;
///
///
/// This interface follows the Terminal.Gui Cancellable Work Pattern (CWP) for lifecycle events
-/// where cancellation makes sense (Stopping, Activating, Deactivating).
+/// where cancellation makes sense (Stopping, Starting).
///
///
public interface IRunnable
@@ -53,60 +53,30 @@ public interface IRunnable
void RaiseStoppingEvent ();
///
- /// Raises the event.
- /// Called by when this runnable is becoming the active session.
+ /// Raises the event.
+ /// Called by or when this runnable session is starting.
///
- /// The previously active runnable being deactivated, or null if none.
- /// if activation was canceled; otherwise .
+ /// if starting was canceled; otherwise .
///
///
- /// This method implements the Cancellable Work Pattern for activation:
+ /// This method implements the Cancellable Work Pattern for starting:
/// 1. Calls virtual method (can cancel)
- /// 2. Raises event (can cancel)
+ /// 2. Raises event (can cancel)
/// 3. If canceled, returns true
- /// 4. If not canceled, calls and returns false
+ /// 4. If not canceled, calls and returns false
///
///
- bool RaiseActivatingEvent (IRunnable? deactivated);
+ bool RaiseStartingEvent ();
///
- /// Raises the event.
- /// Called by after activation succeeds.
+ /// Raises the event.
+ /// Called by after starting succeeds.
///
- /// The previously active runnable that was deactivated, or null if none.
///
- /// This is the post-notification phase of activation. Implementations should raise the
- /// event.
+ /// This is the post-notification phase of starting. Implementations should raise the
+ /// event.
///
- void RaiseActivatedEvent (IRunnable? deactivated);
-
- ///
- /// Raises the event.
- /// Called by when switching to a new runnable.
- ///
- /// The newly activated runnable, or null if none.
- /// if deactivation was canceled; otherwise .
- ///
- ///
- /// This method implements the Cancellable Work Pattern for deactivation:
- /// 1. Calls virtual method (can cancel)
- /// 2. Raises event (can cancel)
- /// 3. If canceled, returns true
- /// 4. If not canceled, calls and returns false
- ///
- ///
- bool RaiseDeactivatingEvent (IRunnable? activated);
-
- ///
- /// Raises the event.
- /// Called by after deactivation succeeds.
- ///
- /// The newly activated runnable, or null if none.
- ///
- /// This is the post-notification phase of deactivation. Implementations should raise the
- /// event.
- ///
- void RaiseDeactivatedEvent (IRunnable? activated);
+ void RaiseStartedEvent ();
#endregion
@@ -162,40 +132,24 @@ public interface IRunnable
event EventHandler? Stopped;
///
- /// Raised when the runnable session is about to become active (the current session).
- /// Can be canceled by setting to .
+ /// Raised when the runnable session is about to start running.
+ /// Can be canceled by setting to .
///
///
- /// Subscribe to this event to prevent activation or to perform pre-activation work.
+ /// Subscribe to this event to prevent starting or to perform pre-start work.
+ /// This aligns with and .
///
- event EventHandler? Activating;
+ event EventHandler? Starting;
///
- /// Raised when the runnable session has become active.
+ /// Raised when the runnable session has started running.
///
///
- /// This is the post-notification event in the Cancellable Work Pattern pair with .
- /// Subscribe to this event for post-activation logic (e.g., setting focus).
+ /// This is the post-notification event in the Cancellable Work Pattern pair with .
+ /// Subscribe to this event for post-start logic (e.g., setting focus).
+ /// This aligns with and .
///
- event EventHandler? Activated;
-
- ///
- /// Raised when the runnable session is about to cease being active (another session is activating).
- /// Can be canceled by setting to .
- ///
- ///
- /// Subscribe to this event to prevent deactivation or to perform pre-deactivation work.
- ///
- event EventHandler? Deactivating;
-
- ///
- /// Raised when the runnable session has ceased being active.
- ///
- ///
- /// This is the post-notification event in the Cancellable Work Pattern pair with .
- /// Subscribe to this event for cleanup or state preservation after deactivation.
- ///
- event EventHandler? Deactivated;
+ event EventHandler? Started;
#endregion
}
diff --git a/Terminal.Gui/App/RunnableEventArgs.cs b/Terminal.Gui/App/RunnableEventArgs.cs
index 3fa6a2b58..797ffe4b1 100644
--- a/Terminal.Gui/App/RunnableEventArgs.cs
+++ b/Terminal.Gui/App/RunnableEventArgs.cs
@@ -1,87 +1,5 @@
namespace Terminal.Gui.App;
-///
-/// Event args for lifecycle events that provide information about the runnable.
-///
-///
-/// Used for post-notification events that cannot be canceled:
-/// and .
-///
-public class RunnableEventArgs : EventArgs
-{
- ///
- /// Initializes a new instance of .
- ///
- /// The runnable involved in the event.
- public RunnableEventArgs (IRunnable runnable)
- {
- Runnable = runnable;
- }
-
- ///
- /// Gets the runnable involved in the event.
- ///
- public IRunnable Runnable { get; }
-}
-
-///
-/// Event args for event. Allows cancellation.
-///
-///
-/// This event is raised when a runnable session is about to become active. It can be canceled
-/// by setting to .
-///
-public class RunnableActivatingEventArgs : System.ComponentModel.CancelEventArgs
-{
- ///
- /// Initializes a new instance of .
- ///
- /// The runnable that is being activated.
- /// The runnable that is being deactivated, or null if none.
- public RunnableActivatingEventArgs (IRunnable activating, IRunnable? deactivated)
- {
- Activating = activating;
- Deactivated = deactivated;
- }
-
- ///
- /// Gets the runnable that is being activated.
- ///
- public IRunnable Activating { get; }
-
- ///
- /// Gets the runnable that is being deactivated, or null if none.
- ///
- public IRunnable? Deactivated { get; }
-}
-
-///
-/// Event args for event. Allows cancellation.
-///
-///
-/// This event is raised when a runnable session is about to cease being active. It can be canceled
-/// by setting to .
-///
-public class RunnableDeactivatingEventArgs : System.ComponentModel.CancelEventArgs
-{
- ///
- /// Initializes a new instance of .
- ///
- /// The runnable that is being deactivated.
- /// The runnable that is being activated, or null if none.
- public RunnableDeactivatingEventArgs (IRunnable deactivating, IRunnable? activated)
- {
- Deactivating = deactivating;
- Activated = activated;
- }
-
- ///
- /// Gets the runnable that is being deactivated.
- ///
- public IRunnable Deactivating { get; }
-
- ///
- /// Gets the runnable that is being activated, or null if none.
- ///
- public IRunnable? Activated { get; }
-}
+// Note: IRunnable lifecycle events (Starting, Started, Stopping, Stopped, Initializing, Initialized)
+// use standard EventArgs or System.ComponentModel.CancelEventArgs.
+// No custom event args are needed for the simplified IRunnable interface.
diff --git a/Terminal.Gui/ViewBase/Runnable.cs b/Terminal.Gui/ViewBase/Runnable.cs
index 37b94b339..feb8f0e02 100644
--- a/Terminal.Gui/ViewBase/Runnable.cs
+++ b/Terminal.Gui/ViewBase/Runnable.cs
@@ -11,8 +11,7 @@ namespace Terminal.Gui.ViewBase;
///
///
/// To customize lifecycle behavior, override the protected virtual methods:
-/// , , ,
-/// , , .
+/// , , , .
///
///
public class Runnable : View, IRunnable
@@ -22,6 +21,42 @@ public class Runnable : View, IRunnable
#region IRunnable Implementation (RaiseXxxEvent Methods)
+ ///
+ public virtual bool RaiseStartingEvent ()
+ {
+ // CWP Phase 1: Pre-notification via virtual method (can cancel)
+ if (OnStarting ())
+ {
+ return true; // Starting canceled
+ }
+
+ // CWP Phase 2: Event notification (can cancel)
+ var args = new System.ComponentModel.CancelEventArgs ();
+ Starting?.Invoke (this, args);
+
+ if (args.Cancel)
+ {
+ return true; // Starting canceled
+ }
+
+ // CWP Phase 3: Perform the work (mark as running)
+ Running = true;
+
+ // CWP Phase 4: Post-notification via virtual method
+ OnStarted ();
+
+ // CWP Phase 5: Post-notification event
+ Started?.Invoke (this, EventArgs.Empty);
+
+ return false; // Starting succeeded
+ }
+
+ ///
+ public virtual void RaiseStartedEvent ()
+ {
+ Started?.Invoke (this, EventArgs.Empty);
+ }
+
///
public virtual void RaiseStoppingEvent ()
{
@@ -50,72 +85,47 @@ public class Runnable : View, IRunnable
Stopped?.Invoke (this, EventArgs.Empty);
}
- ///
- public virtual bool RaiseActivatingEvent (IRunnable? deactivated)
- {
- // CWP Phase 1: Pre-notification via virtual method (can cancel)
- if (OnActivating (deactivated))
- {
- return true; // Activation canceled
- }
-
- // CWP Phase 2: Event notification (can cancel)
- var args = new RunnableActivatingEventArgs (this, deactivated);
- Activating?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return true; // Activation canceled
- }
-
- // CWP Phase 3: Work is done by Application (setting Current)
- // CWP Phase 4 & 5: Call post-notification methods
- OnActivated (deactivated);
-
- return false; // Activation succeeded
- }
-
- ///
- public virtual void RaiseActivatedEvent (IRunnable? deactivated)
- {
- Activated?.Invoke (this, new RunnableEventArgs (this));
- }
-
- ///
- public virtual bool RaiseDeactivatingEvent (IRunnable? activated)
- {
- // CWP Phase 1: Pre-notification via virtual method (can cancel)
- if (OnDeactivating (activated))
- {
- return true; // Deactivation canceled
- }
-
- // CWP Phase 2: Event notification (can cancel)
- var args = new RunnableDeactivatingEventArgs (this, activated);
- Deactivating?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return true; // Deactivation canceled
- }
-
- // CWP Phase 3: Work is done by Application (changing Current)
- // CWP Phase 4 & 5: Call post-notification methods
- OnDeactivated (activated);
-
- return false; // Deactivation succeeded
- }
-
- ///
- public virtual void RaiseDeactivatedEvent (IRunnable? activated)
- {
- Deactivated?.Invoke (this, new RunnableEventArgs (this));
- }
-
#endregion
#region Protected Virtual Methods (Override Pattern)
+ ///
+ /// Called before event. Override to cancel starting.
+ ///
+ /// to cancel; to proceed.
+ ///
+ ///
+ /// This is the first phase of the Cancellable Work Pattern for starting.
+ /// Default implementation returns (allow starting).
+ ///
+ ///
+ /// Override this method to provide custom logic for determining whether the runnable
+ /// should start (e.g., validating preconditions).
+ ///
+ ///
+ protected virtual bool OnStarting ()
+ {
+ return false; // Default: allow starting
+ }
+
+ ///
+ /// Called after session has started. Override for post-start work.
+ ///
+ ///
+ ///
+ /// This is the fourth phase of the Cancellable Work Pattern for starting.
+ /// At this point, is .
+ /// Default implementation does nothing.
+ ///
+ ///
+ /// Override this method to perform work that should occur after the session starts.
+ ///
+ ///
+ protected virtual void OnStarted ()
+ {
+ // Default: do nothing
+ }
+
///
/// Called before event. Override to cancel stopping.
///
@@ -153,110 +163,24 @@ public class Runnable : View, IRunnable
// Default: do nothing
}
- ///
- /// Called before event. Override to cancel activation.
- ///
- /// The previously active runnable being deactivated, or null if none.
- /// to cancel; to proceed.
- ///
- ///
- /// This is the first phase of the Cancellable Work Pattern for activation.
- /// Default implementation returns (allow activation).
- ///
- ///
- /// Override this method to provide custom logic for determining whether the runnable
- /// should become active.
- ///
- ///
- protected virtual bool OnActivating (IRunnable? deactivated)
- {
- return false; // Default: allow activation
- }
-
- ///
- /// Called after activation succeeds. Override for post-activation logic.
- ///
- /// The previously active runnable that was deactivated, or null if none.
- ///
- ///
- /// This is the fourth phase of the Cancellable Work Pattern for activation.
- /// Default implementation calls .
- ///
- ///
- /// Override this method to perform work that should occur after activation
- /// (e.g., setting focus, updating UI). Overrides must call base to ensure the
- /// event is raised.
- ///
- ///
- protected virtual void OnActivated (IRunnable? deactivated)
- {
- RaiseActivatedEvent (deactivated);
- }
-
- ///
- /// Called before event. Override to cancel deactivation.
- ///
- /// The newly activated runnable, or null if none.
- /// to cancel; to proceed.
- ///
- ///
- /// This is the first phase of the Cancellable Work Pattern for deactivation.
- /// Default implementation returns (allow deactivation).
- ///
- ///
- /// Override this method to provide custom logic for determining whether the runnable
- /// should be deactivated (e.g., preventing switching away if unsaved changes exist).
- ///
- ///
- protected virtual bool OnDeactivating (IRunnable? activated)
- {
- return false; // Default: allow deactivation
- }
-
- ///
- /// Called after deactivation succeeds. Override for post-deactivation logic.
- ///
- /// The newly activated runnable, or null if none.
- ///
- ///
- /// This is the fourth phase of the Cancellable Work Pattern for deactivation.
- /// Default implementation calls .
- ///
- ///
- /// Override this method to perform work that should occur after deactivation
- /// (e.g., saving state, releasing resources). Overrides must call base to ensure the
- /// event is raised.
- ///
- ///
- protected virtual void OnDeactivated (IRunnable? activated)
- {
- RaiseDeactivatedEvent (activated);
- }
-
#endregion
#region Events
// Note: Initializing and Initialized events are inherited from View (ISupportInitialize pattern)
+ ///
+ public event EventHandler? Starting;
+
+ ///
+ public event EventHandler? Started;
+
///
public event EventHandler? Stopping;
///
public event EventHandler? Stopped;
- ///
- public event EventHandler? Activating;
-
- ///
- public event EventHandler? Activated;
-
- ///
- public event EventHandler? Deactivating;
-
- ///
- public event EventHandler? Deactivated;
-
#endregion
///
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index 6ebe0a93a..4037d7312 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -7,19 +7,13 @@ namespace Terminal.Gui.Views;
/// scheme.
///
///
-///
/// To run the modally, create the , and pass it to
/// . This will execute the dialog until
/// it terminates via the (`Esc` by default),
/// or when one of the views or buttons added to the dialog calls
/// .
-///
-///
-/// Dialog implements with int? as the result type.
-/// The property contains the index of the button that was clicked, or null if canceled.
-///
///
-public class Dialog : Window, IModalRunnable
+public class Dialog : Window
{
///
/// Initializes a new instance of the class with no s.
@@ -66,24 +60,6 @@ public class Dialog : Window, IModalRunnable
_buttons.Add (button);
Add (button);
-
- // Subscribe to the button's Accept command to set Result
- button.Accepting += Button_Accepting;
- }
-
- private void Button_Accepting (object? sender, CommandEventArgs e)
- {
- // Set Result to the index of the button that was clicked
- if (sender is Button button)
- {
- int index = _buttons.IndexOf (button);
- if (index >= 0)
- {
- Result = index;
- // For backward compatibility, set Canceled = false
- Canceled = false;
- }
- }
}
// TODO: Update button.X = Pos.Justify when alignment changes
@@ -109,14 +85,7 @@ public class Dialog : Window, IModalRunnable
}
/// Gets a value indicating whether the was canceled.
- ///
- /// The default value is .
- ///
- /// Obsolete: Use instead. When is null, the dialog was canceled.
- /// This property is maintained for backward compatibility.
- ///
- ///
- [Obsolete ("Use Result property instead. Result == null indicates the dialog was canceled.")]
+ /// The default value is .
public bool Canceled
{
get { return _canceled; }
@@ -132,29 +101,6 @@ public class Dialog : Window, IModalRunnable
}
}
- ///
- /// Gets or sets the result of the modal dialog operation.
- ///
- ///
- ///
- /// Contains the zero-based index of the button that was clicked to close the dialog,
- /// or null if the dialog was canceled (e.g., ESC key pressed).
- ///
- ///
- /// The button index corresponds to the order buttons were added via or
- /// the initializer.
- ///
- ///
- /// For backward compatibility with the property:
- /// - == null means the dialog was canceled ( == true)
- /// - != null means a button was clicked ( == false)
- ///
- ///
- /// This property implements where TResult is int?.
- ///
- ///
- public int? Result { get; set; }
-
///
/// Defines the default border styling for . Can be configured via
/// .
diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs
index f3228e0e4..fedc501f4 100644
--- a/Terminal.Gui/Views/Toplevel.cs
+++ b/Terminal.Gui/Views/Toplevel.cs
@@ -17,12 +17,8 @@ namespace Terminal.Gui.Views;
/// and run (e.g. s). To run a Toplevel, create the and call
/// .
///
-///
-/// Toplevel implements to support the runnable session lifecycle with proper event handling
-/// following the Cancellable Work Pattern (CWP).
-///
///
-public partial class Toplevel : View, IRunnable
+public partial class Toplevel : View
{
///
/// Initializes a new instance of the class,
@@ -96,60 +92,27 @@ public partial class Toplevel : View, IRunnable
///
public bool IsLoaded { get; private set; }
+ // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate
/// Invoked when the Toplevel active.
- ///
- ///
- /// Obsolete: Use instead. This event is maintained for backward
- /// compatibility and will be raised alongside the new event.
- ///
- ///
- [Obsolete ("Use IRunnable.Activated instead. This event is maintained for backward compatibility.")]
public event EventHandler? Activate;
+ // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate?
/// Invoked when the Toplevel ceases to be active.
- ///
- ///
- /// Obsolete: Use instead. This event is maintained for backward
- /// compatibility and will be raised alongside the new event.
- ///
- ///
- [Obsolete ("Use IRunnable.Deactivated instead. This event is maintained for backward compatibility.")]
public event EventHandler? Deactivate;
/// Invoked when the Toplevel's is closed by .
- ///
- ///
- /// Obsolete: This event is maintained for backward compatibility. The IRunnable architecture
- /// combines this functionality into the event.
- ///
- ///
- [Obsolete ("Use IRunnable.Stopped instead. This event is maintained for backward compatibility.")]
public event EventHandler? Closed;
///
/// Invoked when the Toplevel's is being closed by
/// .
///
- ///
- ///
- /// Obsolete: Use instead. This event is maintained for backward
- /// compatibility and will be raised alongside the new event.
- ///
- ///
- [Obsolete ("Use IRunnable.Stopping instead. This event is maintained for backward compatibility.")]
public event EventHandler? Closing;
///
/// Invoked when the has begun to be loaded. A Loaded event handler
/// is a good place to finalize initialization before calling Run.
///
- ///
- ///
- /// Obsolete: Use instead. The Loaded event conceptually maps to
- /// the Initialized event from the ISupportInitialize pattern, which is now part of IRunnable.
- ///
- ///
- [Obsolete ("Use View.Initialized instead. The Loaded event maps to the Initialized event from ISupportInitialize.")]
public event EventHandler? Loaded;
///
@@ -181,13 +144,6 @@ public partial class Toplevel : View, IRunnable
/// on this .
///
///
- ///
- ///
- /// Obsolete: Use instead. The Ready event is similar to Initialized
- /// but was fired later in the lifecycle. The IRunnable architecture consolidates these into Initialized.
- ///
- ///
- [Obsolete ("Use View.Initialized instead. The Ready event is consolidated into the Initialized event.")]
public event EventHandler? Ready;
///
@@ -203,13 +159,6 @@ public partial class Toplevel : View, IRunnable
/// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place
/// to dispose objects after calling .
///
- ///
- ///
- /// Obsolete: Use instead. The Unloaded event is consolidated into
- /// the Stopped event in the IRunnable architecture.
- ///
- ///
- [Obsolete ("Use IRunnable.Stopped instead. The Unloaded event is consolidated into the Stopped event.")]
public event EventHandler? Unloaded;
internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new (deactivated)); }
@@ -254,226 +203,6 @@ public partial class Toplevel : View, IRunnable
#endregion
- #region IRunnable Implementation
-
- // Note: Running property is already defined above in the Life Cycle region (line 86)
- // Note: Initializing and Initialized events are inherited from View (ISupportInitialize pattern)
-
- ///
- public event EventHandler? Stopping;
-
- ///
- public event EventHandler? Stopped;
-
- ///
- public event EventHandler? Activating;
-
- ///
- public event EventHandler? Activated;
-
- ///
- public event EventHandler? Deactivating;
-
- ///
- public event EventHandler? Deactivated;
-
- ///
- public virtual void RaiseStoppingEvent ()
- {
- // CWP Phase 1: Pre-notification via virtual method (can cancel)
- if (OnStopping ())
- {
- return; // Stopping canceled
- }
-
- // CWP Phase 2: Event notification (can cancel)
- var args = new System.ComponentModel.CancelEventArgs ();
- Stopping?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return; // Stopping canceled
- }
-
- // CWP Phase 3: Perform the work (stop the session)
- Running = false;
-
- // CWP Phase 4: Post-notification via virtual method
- OnStopped ();
-
- // CWP Phase 5: Post-notification event
- Stopped?.Invoke (this, EventArgs.Empty);
- }
-
- ///
- public virtual bool RaiseActivatingEvent (IRunnable? deactivated)
- {
- // CWP Phase 1: Pre-notification via virtual method (can cancel)
- if (OnActivating (deactivated))
- {
- return true; // Activation canceled
- }
-
- // CWP Phase 2: Event notification (can cancel)
- var args = new RunnableActivatingEventArgs (this, deactivated);
- Activating?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return true; // Activation canceled
- }
-
- // CWP Phase 3: Work is done by Application (setting Current)
- // CWP Phase 4 & 5: Call post-notification methods
- OnActivated (deactivated);
-
- return false; // Activation succeeded
- }
-
- ///
- public virtual void RaiseActivatedEvent (IRunnable? deactivated)
- {
- Activated?.Invoke (this, new RunnableEventArgs (this));
- }
-
- ///
- public virtual bool RaiseDeactivatingEvent (IRunnable? activated)
- {
- // CWP Phase 1: Pre-notification via virtual method (can cancel)
- if (OnDeactivating (activated))
- {
- return true; // Deactivation canceled
- }
-
- // CWP Phase 2: Event notification (can cancel)
- var args = new RunnableDeactivatingEventArgs (this, activated);
- Deactivating?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return true; // Deactivation canceled
- }
-
- // CWP Phase 3: Work is done by Application (changing Current)
- // CWP Phase 4 & 5: Call post-notification methods
- OnDeactivated (activated);
-
- return false; // Deactivation succeeded
- }
-
- ///
- public virtual void RaiseDeactivatedEvent (IRunnable? activated)
- {
- Deactivated?.Invoke (this, new RunnableEventArgs (this));
- }
-
- ///
- /// Called before event. Override to cancel stopping.
- ///
- /// to cancel; to proceed.
- ///
- ///
- /// This is the first phase of the Cancellable Work Pattern for stopping.
- /// Default implementation calls the legacy method for backward compatibility.
- ///
- ///
- protected virtual bool OnStopping ()
- {
- // For backward compatibility, delegate to legacy OnClosing method
- var ev = new ToplevelClosingEventArgs (this);
- return OnClosing (ev);
- }
-
- ///
- /// Called after session has stopped. Override for post-stop cleanup.
- ///
- ///
- /// Default implementation does nothing. For backward compatibility, the legacy
- /// event is raised by Application.End().
- ///
- protected virtual void OnStopped ()
- {
- // Default: do nothing
- // Note: Legacy Closed event is raised by Application.End()
- }
-
- ///
- /// Called before event. Override to cancel activation.
- ///
- /// The previously active runnable being deactivated, or null if none.
- /// to cancel; to proceed.
- ///
- /// Default implementation returns false (allow activation). For backward compatibility,
- /// the legacy method is called after activation succeeds.
- ///
- protected virtual bool OnActivating (IRunnable? deactivated)
- {
- return false; // Default: allow activation
- }
-
- ///
- /// Called after activation succeeds. Override for post-activation logic.
- ///
- /// The previously active runnable that was deactivated, or null if none.
- ///
- /// Default implementation raises the event and calls the legacy
- /// method for backward compatibility.
- ///
- protected virtual void OnActivated (IRunnable? deactivated)
- {
- RaiseActivatedEvent (deactivated);
-
- // For backward compatibility, call legacy OnActivate if deactivated is a Toplevel
- if (deactivated is Toplevel tl)
- {
- OnActivate (tl);
- }
- else
- {
- // If not a Toplevel, still raise the legacy Activate event with null
- Activate?.Invoke (this, new ToplevelEventArgs (null));
- }
- }
-
- ///
- /// Called before event. Override to cancel deactivation.
- ///
- /// The newly activated runnable, or null if none.
- /// to cancel; to proceed.
- ///
- /// Default implementation returns false (allow deactivation).
- ///
- protected virtual bool OnDeactivating (IRunnable? activated)
- {
- return false; // Default: allow deactivation
- }
-
- ///
- /// Called after deactivation succeeds. Override for post-deactivation logic.
- ///
- /// The newly activated runnable, or null if none.
- ///
- /// Default implementation raises the event and calls the legacy
- /// method for backward compatibility.
- ///
- protected virtual void OnDeactivated (IRunnable? activated)
- {
- RaiseDeactivatedEvent (activated);
-
- // For backward compatibility, call legacy OnDeactivate if activated is a Toplevel
- if (activated is Toplevel tl)
- {
- OnDeactivate (tl);
- }
- else
- {
- // If not a Toplevel, still raise the legacy Deactivate event with null
- Deactivate?.Invoke (this, new ToplevelEventArgs (null));
- }
- }
-
- #endregion
-
#region Size / Position Management
// TODO: Make cancelable?
diff --git a/Tests/UnitTests/Views/IRunnableTests.cs b/Tests/UnitTests/Views/IRunnableTests.cs
deleted file mode 100644
index d6c9db6e3..000000000
--- a/Tests/UnitTests/Views/IRunnableTests.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using Xunit;
-using Xunit.Abstractions;
-
-namespace UnitTests.ViewsTests;
-
-public class IRunnableTests (ITestOutputHelper output)
-{
- private readonly ITestOutputHelper _output = output;
-
- [Fact]
- public void Toplevel_Implements_IRunnable ()
- {
- var top = new Toplevel ();
- Assert.IsAssignableFrom (top);
- }
-
- [Fact]
- public void Dialog_Implements_IModalRunnable ()
- {
- var dialog = new Dialog ();
- Assert.IsAssignableFrom (dialog);
- Assert.IsAssignableFrom> (dialog);
- }
-
- [Fact]
- public void Runnable_Base_Class_Works ()
- {
- var runnable = new Runnable ();
- Assert.IsAssignableFrom (runnable);
- Assert.False (runnable.Running);
- }
-
- [Fact]
- [AutoInitShutdown]
- public void Dialog_Result_Property_Works ()
- {
- var dialog = new Dialog ();
- var btn1 = new Button { Text = "OK" };
- var btn2 = new Button { Text = "Cancel" };
-
- dialog.AddButton (btn1);
- dialog.AddButton (btn2);
-
- // Result should be null by default
- Assert.Null (dialog.Result);
-
- // Simulate clicking the first button by invoking Accept
- btn1.InvokeCommand (Command.Accept);
-
- // Result should now be 0 (first button)
- Assert.Equal (0, dialog.Result);
-
- // Reset
- dialog.Result = null;
-
- // Simulate clicking the second button
- btn2.InvokeCommand (Command.Accept);
-
- // Result should now be 1 (second button)
- Assert.Equal (1, dialog.Result);
- }
-
- [Fact]
- [AutoInitShutdown]
- public void Toplevel_Legacy_Events_Still_Work ()
- {
- // Verify that legacy Toplevel events still fire for backward compatibility
- var eventsRaised = new List ();
- var top = new Toplevel ();
-
-#pragma warning disable CS0618 // Type or member is obsolete
- top.Activate += (s, e) => eventsRaised.Add ("Activate");
-#pragma warning restore CS0618
-
- var token = Application.Begin (top);
-
- // Legacy event should still fire
- Assert.Contains ("Activate", eventsRaised);
-
- Application.End (token);
- }
-
- [Fact]
- public void IRunnable_Stopping_Event_Works ()
- {
- // Test that the Stopping event can be subscribed to and fires
- var top = new Toplevel ();
- var stoppingFired = false;
- var stoppedFired = false;
-
- top.Stopping += (s, e) => { stoppingFired = true; };
- top.Stopped += (s, e) => { stoppedFired = true; };
-
- top.Running = true;
- top.RaiseStoppingEvent ();
-
- Assert.True (stoppingFired);
- Assert.True (stoppedFired);
- Assert.False (top.Running);
- }
-
- [Fact]
- public void IRunnable_Stopping_Can_Be_Canceled ()
- {
- // Test that the Stopping event can cancel the stop operation
- var top = new Toplevel ();
- var stoppingFired = false;
- var stoppedFired = false;
-
- top.Stopping += (s, e) =>
- {
- stoppingFired = true;
- e.Cancel = true; // Cancel the stop
- };
-
- top.Stopped += (s, e) => { stoppedFired = true; };
-
- top.Running = true;
- top.RaiseStoppingEvent ();
-
- Assert.True (stoppingFired);
- Assert.False (stoppedFired); // Should not fire because it was canceled
- Assert.True (top.Running); // Should still be running
- }
-
- [Fact]
- [AutoInitShutdown]
- public void IRunnable_Initialization_Events_Fire ()
- {
- // Test that Initializing and Initialized events fire
- var initializingFired = false;
- var initializedFired = false;
- var top = new Toplevel ();
-
- top.Initializing += (s, e) => { initializingFired = true; };
- top.Initialized += (s, e) => { initializedFired = true; };
-
- var token = Application.Begin (top);
-
- Assert.True (initializingFired);
- Assert.True (initializedFired);
-
- Application.End (token);
- }
-
- [Fact]
- public void IRunnable_Activating_And_Activated_Events_Can_Fire ()
- {
- // Test that manually calling the activation methods works
- var activatingFired = false;
- var activatedFired = false;
- var top = new Toplevel ();
-
- top.Activating += (s, e) => { activatingFired = true; };
- top.Activated += (s, e) => { activatedFired = true; };
-
- // Manually trigger activation
- bool canceled = top.RaiseActivatingEvent (null);
-
- Assert.False (canceled);
- Assert.True (activatingFired);
- Assert.True (activatedFired);
- }
-}