Files
Terminal.Gui/Examples/UICatalog/Scenarios/FileDialogExamples.cs
Tig 0f72cf8a74 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.
2025-12-01 14:40:31 -07:00

283 lines
11 KiB
C#

using System.IO.Abstractions;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("FileDialog", "Demonstrates how to the FileDialog class")]
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Files and IO")]
public class FileDialogExamples : Scenario
{
private CheckBox _cbAllowMultipleSelection;
private CheckBox _cbAlwaysTableShowHeaders;
private CheckBox _cbCaseSensitive;
private CheckBox _cbDrivesOnlyInTree;
private CheckBox _cbPreserveFilenameOnDirectoryChanges;
private CheckBox _cbFlipButtonOrder;
private CheckBox _cbMustExist;
private CheckBox _cbShowTreeBranchLines;
private CheckBox _cbUseColors;
private OptionSelector _osAllowedTypes;
private OptionSelector _osCaption;
private OptionSelector _osIcons;
private OptionSelector _osOpenMode;
private TextField _tbCancelButton;
private TextField _tbOkButton;
public override void Main ()
{
Application.Init ();
var y = 0;
var x = 1;
var win = new Window { Title = GetQuitKeyAndName () };
_cbMustExist = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
win.Add (_cbMustExist);
_cbUseColors = new ()
{ CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
win.Add (_cbUseColors);
_cbCaseSensitive = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
win.Add (_cbCaseSensitive);
_cbAllowMultipleSelection = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
win.Add (_cbAllowMultipleSelection);
_cbShowTreeBranchLines = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
win.Add (_cbShowTreeBranchLines);
_cbAlwaysTableShowHeaders = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
win.Add (_cbAlwaysTableShowHeaders);
_cbDrivesOnlyInTree = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
win.Add (_cbDrivesOnlyInTree);
_cbPreserveFilenameOnDirectoryChanges = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
win.Add (_cbPreserveFilenameOnDirectoryChanges);
y = 0;
x = 24;
win.Add (
new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
);
win.Add (new Label { X = x++, Y = y++, Text = "Caption" });
_osCaption = new () { X = x, Y = y };
_osCaption.Labels = ["_Ok", "O_pen", "_Save"];
win.Add (_osCaption);
y = 0;
x = 34;
win.Add (
new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
);
win.Add (new Label { X = x++, Y = y++, Text = "OpenMode" });
_osOpenMode = new () { X = x, Y = y };
_osOpenMode.Labels = ["_File", "D_irectory", "_Mixed"];
win.Add (_osOpenMode);
y = 0;
x = 48;
win.Add (
new Line { Orientation = Orientation.Vertical, X = x++, Y = 1, Height = 4 }
);
win.Add (new Label { X = x++, Y = y++, Text = "Icons" });
_osIcons = new () { X = x, Y = y };
_osIcons.Labels = ["_None", "_Unicode", "Nerd_*"];
win.Add (_osIcons);
win.Add (new Label { Y = Pos.AnchorEnd (2), Text = "* Requires installing Nerd fonts" });
win.Add (new Label { Y = Pos.AnchorEnd (1), Text = " (see: https://github.com/devblackops/Terminal-Icons)" });
y = 5;
x = 24;
win.Add (
new Line { Orientation = Orientation.Vertical, X = x++, Y = y + 1, Height = 4 }
);
win.Add (new Label { X = x++, Y = y++, Text = "Allowed" });
_osAllowedTypes = new () { X = x, Y = y };
_osAllowedTypes.Labels = ["An_y", "Cs_v (Recommended)", "Csv (S_trict)"];
win.Add (_osAllowedTypes);
y = 5;
x = 45;
win.Add (
new Line { Orientation = Orientation.Vertical, X = x++, Y = y + 1, Height = 4 }
);
win.Add (new Label { X = x++, Y = y++, Text = "Buttons" });
win.Add (new Label { X = x, Y = y++, Text = "O_k Text:" });
_tbOkButton = new () { X = x, Y = y++, Width = 12 };
win.Add (_tbOkButton);
win.Add (new Label { X = x, Y = y++, Text = "_Cancel Text:" });
_tbCancelButton = new () { X = x, Y = y++, Width = 12 };
win.Add (_tbCancelButton);
_cbFlipButtonOrder = new () { X = x, Y = y++, Text = "Flip Ord_er" };
win.Add (_cbFlipButtonOrder);
var btn = new Button { X = 1, Y = 9, IsDefault = true, Text = "Run Dialog" };
win.Accepting += (s, e) =>
{
try
{
CreateDialog ();
}
catch (Exception ex)
{
MessageBox.ErrorQuery (Application.Instance, "Error", ex.ToString (), "_Ok");
}
finally
{
e.Handled = true;
}
};
win.Add (btn);
Application.Run (win);
win.Dispose ();
Application.Shutdown ();
}
private void ConfirmOverwrite (object sender, FilesSelectedEventArgs e)
{
if (!string.IsNullOrWhiteSpace (e.Dialog.Path))
{
if (File.Exists (e.Dialog.Path))
{
int? result = MessageBox.Query (Application.Instance, "Overwrite?", "File already exists", "_Yes", "_No");
e.Cancel = result == 1;
}
}
}
private void CreateDialog ()
{
if (_osOpenMode.Value is { })
{
var fd = new FileDialog
{
OpenMode = Enum.Parse<OpenMode> (
_osOpenMode.Labels
.Select (l => TextFormatter.FindHotKey (l, _osOpenMode.HotKeySpecifier, out int hotPos, out Key _)
// Remove the hotkey specifier at the found position
? TextFormatter.RemoveHotKeySpecifier (l, hotPos, _osOpenMode.HotKeySpecifier)
// No hotkey found, return the label as is
: l)
.ToArray () [_osOpenMode.Value.Value]
),
MustExist = _cbMustExist.CheckedState == CheckState.Checked,
AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
};
fd.Style.OkButtonText =
_osCaption.Labels.Select (l => TextFormatter.RemoveHotKeySpecifier (l, 0, _osCaption.HotKeySpecifier)).ToArray ()
[_osCaption.Value!.Value];
// If Save style dialog then give them an overwrite prompt
if (_osCaption.Value == 2)
{
fd.FilesSelected += ConfirmOverwrite;
}
fd.Style.IconProvider.UseUnicodeCharacters = _osIcons.Value == 1;
fd.Style.IconProvider.UseNerdIcons = _osIcons.Value == 2;
if (_cbCaseSensitive.CheckedState == CheckState.Checked)
{
fd.SearchMatcher = new CaseSensitiveSearchMatcher ();
}
fd.Style.UseColors = _cbUseColors.CheckedState == CheckState.Checked;
fd.Style.TreeStyle.ShowBranchLines = _cbShowTreeBranchLines.CheckedState == CheckState.Checked;
fd.Style.TableStyle.AlwaysShowHeaders = _cbAlwaysTableShowHeaders.CheckedState == CheckState.Checked;
IDirectoryInfoFactory dirInfoFactory = new FileSystem ().DirectoryInfo;
if (_cbDrivesOnlyInTree.CheckedState == CheckState.Checked)
{
fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
}
fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;
if (_osAllowedTypes.Value > 0)
{
fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
if (_osAllowedTypes.Value == 1)
{
fd.AllowedTypes.Insert (1, new AllowedTypeAny ());
}
}
if (!string.IsNullOrWhiteSpace (_tbOkButton.Text))
{
fd.Style.OkButtonText = _tbOkButton.Text;
}
if (!string.IsNullOrWhiteSpace (_tbCancelButton.Text))
{
fd.Style.CancelButtonText = _tbCancelButton.Text;
}
if (_cbFlipButtonOrder.CheckedState == CheckState.Checked)
{
fd.Style.FlipOkCancelButtonLayoutOrder = true;
}
Application.Run (fd);
bool canceled = fd.Canceled;
IReadOnlyList<string> multiSelected = fd.MultiSelected;
string path = fd.Path;
// This needs to be disposed before opening other runnable
fd.Dispose ();
if (canceled)
{
MessageBox.Query (Application.Instance,
"Canceled",
"You canceled navigation and did not pick anything",
"Ok"
);
}
else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
{
MessageBox.Query (Application.Instance,
"Chosen!",
"You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
"Ok"
);
}
else
{
MessageBox.Query (Application.Instance,
"Chosen!",
"You chose:" + Environment.NewLine + path,
"Ok"
);
}
}
}
private class CaseSensitiveSearchMatcher : ISearchMatcher
{
private string _terms;
public void Initialize (string terms) { _terms = terms; }
public bool IsMatch (IFileSystemInfo f) { return f.Name.Contains (_terms, StringComparison.CurrentCulture); }
}
}