Fixes #4425 - ApplicationImpl internal (#4426)

* Pulled from v2_release

* Refactor migration guide for Terminal.Gui v2

Restructured and expanded the migration guide to provide a comprehensive resource for transitioning from Terminal.Gui v1 to v2. Key updates include:

- Added a Table of Contents for easier navigation.
- Summarized major architectural changes in v2, including the instance-based application model, IRunnable architecture, and 24-bit TrueColor support.
- Updated examples to reflect new patterns, such as initializers replacing constructors and explicit disposal using `IDisposable`.
- Documented changes to the layout system, including the removal of `Absolute`/`Computed` styles and the introduction of `Viewport`.
- Standardized event patterns to use `object sender, EventArgs args`.
- Detailed updates to the Keyboard, Mouse, and Navigation APIs, including configurable key bindings and viewport-relative mouse coordinates.
- Replaced legacy components like `ScrollView` and `ContextMenu` with built-in scrolling and `PopoverMenu`.
- Clarified disposal rules and introduced best practices for resource management.
- Provided a complete migration example and a summary of breaking changes.

This update aims to simplify the migration process by addressing breaking changes, introducing new features, and aligning with modern .NET conventions.

* Refactor to use Application.Instance for lifecycle management

Replaced all occurrences of `ApplicationImpl.Instance` with the new `Application.Instance` property across the codebase to align with the updated application lifecycle model.

Encapsulated the `ApplicationImpl` class by making it `internal`, ensuring it is no longer directly accessible outside its assembly. Introduced the `[Obsolete]` `Application.Instance` property as a backward-compatible singleton for the legacy static `Application` model, while encouraging the use of `Application.Create()` for new code.

Updated `MessageBox` methods to use `Application.Instance` for consistent modal dialog management. Improved documentation to reflect these changes and emphasize the transition to the instance-based application model.

Performed code cleanup in multiple classes to ensure consistency and maintainability. These changes maintain backward compatibility while preparing the codebase for the eventual removal of the legacy `ApplicationImpl` class.

* Fix doc bug

* - Removed obsolete `.cd` class diagram files.
- Introduced `IRunnable` interface for decoupling component execution.
- Added fluent API for running dialogs and retrieving results.
- Enhanced `View` with `App` and `Driver` properties for better decoupling.
- Improved testability with support for mock and real applications.
- Implemented `IDisposable` for proper resource cleanup.
- Replaced `RunnableSessionStack` with `SessionStack` for session management.
- Updated driver architecture to align with the new model.
- Scoped `IKeyboard` to application contexts for modularity.
- Updated documentation with migration strategies and best practices.

These changes modernize the library, improve maintainability, and align with current development practices.
This commit is contained in:
Tig
2025-12-01 14:40:31 -07:00
committed by GitHub
parent 8e92327dbe
commit 0f72cf8a74
34 changed files with 278 additions and 833 deletions

View File

@@ -49,7 +49,7 @@ public class ContextMenus : Scenario
var text = "Context Menu";
var width = 20;
CreateWinContextMenu (ApplicationImpl.Instance);
CreateWinContextMenu (Application.Instance);
var label = new Label
{

View File

@@ -215,7 +215,7 @@ public class CsvEditor : Scenario
_tableView.Table.Columns
);
int? result = MessageBox.Query (ApplicationImpl.Instance,
int? result = MessageBox.Query (Application.Instance,
"Column Type",
"Pick a data type for the column",
"Date",
@@ -308,7 +308,7 @@ public class CsvEditor : Scenario
if (_tableView.SelectedColumn == -1)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No Column", "No column selected", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No Column", "No column selected", "Ok");
return;
}
@@ -320,7 +320,7 @@ public class CsvEditor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Could not remove column", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Could not remove column", ex.Message, "Ok");
}
}
@@ -342,7 +342,7 @@ public class CsvEditor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, 60, 20, "Failed to set text", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, 60, 20, "Failed to set text", ex.Message, "Ok");
}
_tableView.Update ();
@@ -388,7 +388,7 @@ public class CsvEditor : Scenario
if (_tableView.SelectedColumn == -1)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No Column", "No column selected", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No Column", "No column selected", "Ok");
return;
}
@@ -413,7 +413,7 @@ public class CsvEditor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error moving column", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Error moving column", ex.Message, "Ok");
}
}
@@ -426,7 +426,7 @@ public class CsvEditor : Scenario
if (_tableView.SelectedRow == -1)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No Rows", "No row selected", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No Rows", "No row selected", "Ok");
return;
}
@@ -462,7 +462,7 @@ public class CsvEditor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error moving column", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Error moving column", ex.Message, "Ok");
}
}
@@ -470,7 +470,7 @@ public class CsvEditor : Scenario
{
if (_tableView?.Table is null)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No Table Loaded", "No table has currently be opened", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No Table Loaded", "No table has currently be opened", "Ok");
return true;
}
@@ -582,7 +582,7 @@ public class CsvEditor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance,
MessageBox.ErrorQuery (Application.Instance,
"Open Failed",
$"Error on line {lineNumber}{Environment.NewLine}{ex.Message}",
"Ok"
@@ -612,7 +612,7 @@ public class CsvEditor : Scenario
{
if (_tableView?.Table is null || string.IsNullOrWhiteSpace (_currentFile) || _currentTable is null)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No file loaded", "No file is currently loaded", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No file loaded", "No file is currently loaded", "Ok");
return;
}
@@ -674,7 +674,7 @@ public class CsvEditor : Scenario
if (col.DataType == typeof (string))
{
MessageBox.ErrorQuery (ApplicationImpl.Instance,
MessageBox.ErrorQuery (Application.Instance,
"Cannot Format Column",
"String columns cannot be Formatted, try adding a new column to the table with a date/numerical Type",
"Ok"
@@ -711,7 +711,7 @@ public class CsvEditor : Scenario
if (_tableView.SelectedColumn == -1)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "No Column", "No column selected", "Ok");
MessageBox.ErrorQuery (Application.Instance, "No Column", "No column selected", "Ok");
return;
}

View File

@@ -79,7 +79,7 @@ public class DynamicStatusBar : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Binding Error", $"Binding failed: {ex}.", "Ok");
MessageBox.ErrorQuery (Application.Instance, "Binding Error", $"Binding failed: {ex}.", "Ok");
}
}
}
@@ -140,7 +140,7 @@ public class DynamicStatusBar : Scenario
public TextView TextAction { get; }
public TextField TextShortcut { get; }
public TextField TextTitle { get; }
public Action CreateAction (DynamicStatusItem item) { return () => MessageBox.ErrorQuery (ApplicationImpl.Instance, item.Title, item.Action, "Ok"); }
public Action CreateAction (DynamicStatusItem item) { return () => MessageBox.ErrorQuery (Application.Instance, item.Title, item.Action, "Ok"); }
public void EditStatusItem (Shortcut statusItem)
{

View File

@@ -201,7 +201,7 @@ public class Editor : Scenario
Debug.Assert (_textView.IsDirty);
int? r = MessageBox.ErrorQuery (
ApplicationImpl.Instance,
Application.Instance,
"Save File",
$"Do you want save changes in {_appWindow.Title}?",
"Yes",
@@ -236,7 +236,7 @@ public class Editor : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Error", ex.Message, "Ok");
}
}
@@ -315,11 +315,11 @@ public class Editor : Scenario
if (!found)
{
MessageBox.Query (ApplicationImpl.Instance, "Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
MessageBox.Query (Application.Instance, "Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
}
else if (gaveFullTurn)
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Find",
$"No more occurrences were found for the following specified text: '{_textToFind}'",
"Ok"
@@ -895,7 +895,7 @@ public class Editor : Scenario
if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace))
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Replace All",
$"All occurrences were replaced for the following specified text: '{_textToReplace}'",
"Ok"
@@ -903,7 +903,7 @@ public class Editor : Scenario
}
else
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Replace All",
$"None of the following specified text was found: '{_textToFind}'",
"Ok"
@@ -1155,7 +1155,7 @@ public class Editor : Scenario
{
if (File.Exists (path))
{
if (MessageBox.Query (ApplicationImpl.Instance,
if (MessageBox.Query (Application.Instance,
"Save File",
"File already exists. Overwrite any way?",
"No",
@@ -1194,11 +1194,11 @@ public class Editor : Scenario
_originalText = Encoding.Unicode.GetBytes (_textView.Text);
_saved = true;
_textView.ClearHistoryChanges ();
MessageBox.Query (ApplicationImpl.Instance, "Save File", "File was successfully saved.", "Ok");
MessageBox.Query (Application.Instance, "Save File", "File was successfully saved.", "Ok");
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Error", ex.Message, "Ok");
return false;
}

View File

@@ -133,7 +133,7 @@ public class FileDialogExamples : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error", ex.ToString (), "_Ok");
MessageBox.ErrorQuery (Application.Instance, "Error", ex.ToString (), "_Ok");
}
finally
{
@@ -153,7 +153,7 @@ public class FileDialogExamples : Scenario
{
if (File.Exists (e.Dialog.Path))
{
int? result = MessageBox.Query (ApplicationImpl.Instance, "Overwrite?", "File already exists", "_Yes", "_No");
int? result = MessageBox.Query (Application.Instance, "Overwrite?", "File already exists", "_Yes", "_No");
e.Cancel = result == 1;
}
}
@@ -248,7 +248,7 @@ public class FileDialogExamples : Scenario
if (canceled)
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Canceled",
"You canceled navigation and did not pick anything",
"Ok"
@@ -256,7 +256,7 @@ public class FileDialogExamples : Scenario
}
else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Chosen!",
"You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
"Ok"
@@ -264,7 +264,7 @@ public class FileDialogExamples : Scenario
}
else
{
MessageBox.Query (ApplicationImpl.Instance,
MessageBox.Query (Application.Instance,
"Chosen!",
"You chose:" + Environment.NewLine + path,
"Ok"

View File

@@ -29,7 +29,7 @@ public sealed class Generic : Scenario
{
// When Accepting is handled, set e.Handled to true to prevent further processing.
e.Handled = true;
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Error", "You pressed the button!", "_Ok");
MessageBox.ErrorQuery (Application.Instance, "Error", "You pressed the button!", "_Ok");
};
appWindow.Add (button);

View File

@@ -181,7 +181,7 @@ public class HexEditor : Scenario
}
}
private void Copy () { MessageBox.ErrorQuery (ApplicationImpl.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private void Copy () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private void CreateDemoFile (string fileName)
{
@@ -208,7 +208,7 @@ public class HexEditor : Scenario
ms.Close ();
}
private void Cut () { MessageBox.ErrorQuery (ApplicationImpl.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private void Cut () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private Stream LoadFile ()
{
@@ -216,7 +216,7 @@ public class HexEditor : Scenario
if (!_saved && _hexView!.Edits.Count > 0 && _hexView.Source is {})
{
if (MessageBox.ErrorQuery (ApplicationImpl.Instance,
if (MessageBox.ErrorQuery (Application.Instance,
"Save",
"The changes were not saved. Want to open without saving?",
"_Yes",
@@ -267,7 +267,7 @@ public class HexEditor : Scenario
d.Dispose ();
}
private void Paste () { MessageBox.ErrorQuery (ApplicationImpl.Instance, "Not Implemented", "Functionality not yet implemented.", "_Ok"); }
private void Paste () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "_Ok"); }
private void Quit () { Application.RequestStop (); }
private void Save ()

View File

@@ -183,7 +183,7 @@ public class Images : Scenario
if (!_sixelSupportResult.SupportsTransparency)
{
if (MessageBox.Query (ApplicationImpl.Instance,
if (MessageBox.Query (Application.Instance,
"Transparency Not Supported",
"It looks like your terminal does not support transparent sixel backgrounds. Do you want to try anyway?",
"Yes",
@@ -288,7 +288,7 @@ public class Images : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, "Could not open file", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, "Could not open file", ex.Message, "Ok");
return;
}
@@ -492,7 +492,7 @@ public class Images : Scenario
{
if (_imageView.FullResImage == null)
{
MessageBox.Query (ApplicationImpl.Instance, "No Image Loaded", "You must first open an image. Use the 'Open Image' button above.", "Ok");
MessageBox.Query (Application.Instance, "No Image Loaded", "You must first open an image. Use the 'Open Image' button above.", "Ok");
return;
}

View File

@@ -173,7 +173,7 @@ public class InteractiveTree : Scenario
if (parent is null)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance,
MessageBox.ErrorQuery (Application.Instance,
"Could not delete",
$"Parent of '{toDelete}' was unexpectedly null",
"Ok"

View File

@@ -164,17 +164,17 @@ public class KeyBindingsDemo : View
AddCommand (Command.Save, ctx =>
{
MessageBox.Query (ApplicationImpl.Instance, $"{ctx.Command}", $"Ctx: {ctx}", buttons: "Ok");
MessageBox.Query (Application.Instance, $"{ctx.Command}", $"Ctx: {ctx}", buttons: "Ok");
return true;
});
AddCommand (Command.New, ctx =>
{
MessageBox.Query (ApplicationImpl.Instance, $"{ctx.Command}", $"Ctx: {ctx}", buttons: "Ok");
MessageBox.Query (Application.Instance, $"{ctx.Command}", $"Ctx: {ctx}", buttons: "Ok");
return true;
});
AddCommand (Command.HotKey, ctx =>
{
MessageBox.Query (ApplicationImpl.Instance, $"{ctx.Command}", $"Ctx: {ctx}\nCommand: {ctx.Command}", buttons: "Ok");
MessageBox.Query (Application.Instance, $"{ctx.Command}", $"Ctx: {ctx}\nCommand: {ctx.Command}", buttons: "Ok");
SetFocus ();
return true;
});
@@ -189,7 +189,7 @@ public class KeyBindingsDemo : View
{
return false;
}
MessageBox.Query (ApplicationImpl.Instance, $"{keyCommandContext.Binding}", $"Key: {keyCommandContext.Binding.Key}\nCommand: {ctx.Command}", buttons: "Ok");
MessageBox.Query (Application.Instance, $"{keyCommandContext.Binding}", $"Key: {keyCommandContext.Binding.Key}\nCommand: {ctx.Command}", buttons: "Ok");
Application.RequestStop ();
return true;
});

View File

@@ -336,7 +336,7 @@ public class ListColumns : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, 60, 20, "Failed to set", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, 60, 20, "Failed to set", ex.Message, "Ok");
}
}
}

View File

@@ -251,7 +251,7 @@ public class MessageBoxes : Scenario
{
buttonPressedLabel.Text =
$"{MessageBox.Query (
ApplicationImpl.Instance, width,
Application.Instance, width,
height,
titleEdit.Text,
messageEdit.Text,
@@ -263,7 +263,7 @@ public class MessageBoxes : Scenario
else
{
buttonPressedLabel.Text =
$"{MessageBox.ErrorQuery (ApplicationImpl.Instance,
$"{MessageBox.ErrorQuery (Application.Instance,
width,
height,
titleEdit.Text,

View File

@@ -99,7 +99,7 @@ public class MultiColouredTable : Scenario
}
catch (Exception ex)
{
MessageBox.ErrorQuery (ApplicationImpl.Instance, 60, 20, "Failed to set text", ex.Message, "Ok");
MessageBox.ErrorQuery (Application.Instance, 60, 20, "Failed to set text", ex.Message, "Ok");
}
_tableView.Update ();

View File

@@ -59,7 +59,7 @@ public class Navigation : Scenario
Y = 0,
Title = $"TopButton _{GetNextHotKey ()}"
};
button.Accepting += (sender, args) => MessageBox.Query (ApplicationImpl.Instance, "hi", button.Title, "_Ok");
button.Accepting += (sender, args) => MessageBox.Query (Application.Instance, "hi", button.Title, "_Ok");
testFrame.Add (button);

View File

@@ -71,7 +71,7 @@ public class Notepad : Scenario
new MenuItem
{
Title = "_About",
Action = () => MessageBox.Query (ApplicationImpl.Instance, "Notepad", "About Notepad...", "Ok")
Action = () => MessageBox.Query (Application.Instance, "Notepad", "About Notepad...", "Ok")
}
]
)
@@ -196,7 +196,7 @@ public class Notepad : Scenario
if (tab.UnsavedChanges)
{
int? result = MessageBox.Query (ApplicationImpl.Instance,
int? result = MessageBox.Query (Application.Instance,
"Save Changes",
$"Save changes to {tab.Text.TrimEnd ('*')}",
"Yes",

View File

@@ -166,7 +166,7 @@ public class RuneWidthGreaterThanOne : Scenario
{
if (_text is { })
{
MessageBox.Query (ApplicationImpl.Instance, "Say Hello 你", $"Hello {_text.Text}", "Ok");
MessageBox.Query (Application.Instance, "Say Hello 你", $"Hello {_text.Text}", "Ok");
}
}
@@ -197,7 +197,7 @@ public class RuneWidthGreaterThanOne : Scenario
{
if (_text is { })
{
MessageBox.Query (ApplicationImpl.Instance, "Say Hello", $"Hello {_text.Text}", "Ok");
MessageBox.Query (Application.Instance, "Say Hello", $"Hello {_text.Text}", "Ok");
}
}
@@ -252,7 +252,7 @@ public class RuneWidthGreaterThanOne : Scenario
{
if (_text is { })
{
MessageBox.Query (ApplicationImpl.Instance, "こんにちはと言う", $"こんにちは {_text.Text}", "Ok");
MessageBox.Query (Application.Instance, "こんにちはと言う", $"こんにちは {_text.Text}", "Ok");
}
}

View File

@@ -18,7 +18,7 @@ public class WindowsAndFrameViews : Scenario
static int? About ()
{
return MessageBox.Query (ApplicationImpl.Instance,
return MessageBox.Query (Application.Instance,
"About UI Catalog",
"UI Catalog is a comprehensive sample library for Terminal.Gui",
"Ok"

View File

@@ -21,7 +21,7 @@ public class WizardAsView : Scenario
{
Title = "_Restart Configuration...",
Action = () => MessageBox.Query (
ApplicationImpl.Instance,
Application.Instance,
"Wizard",
"Are you sure you want to reset the Wizard and start over?",
"Ok",
@@ -32,7 +32,7 @@ public class WizardAsView : Scenario
{
Title = "Re_boot Server...",
Action = () => MessageBox.Query (
ApplicationImpl.Instance,
Application.Instance,
"Wizard",
"Are you sure you want to reboot the server start over?",
"Ok",
@@ -43,7 +43,7 @@ public class WizardAsView : Scenario
{
Title = "_Shutdown Server...",
Action = () => MessageBox.Query (
ApplicationImpl.Instance,
Application.Instance,
"Wizard",
"Are you sure you want to cancel setup and shutdown?",
"Ok",