Przeglądaj źródła

Fixes #4305 - 'FileDialog` TreeView (#4306)

* Partial fix - probably breaks stuff

Refactored `FileDialog.cs` by replacing `treeViewContainer` with `_treeView`, adjusting UI component positions, and adding style properties to `_tableView`. Improved user interaction with new event handlers and key bindings. Rearranged `base.Add` calls to reflect the updated UI hierarchy.

* Tweaked Dialog and FileDialog attribute handling

Removed setting of _tableView.Style.InvertSelectedCellFirstCharacter = true;

Commented out custom attribute handling in Dialog.cs for VisualRole.Normal and VisualRole.Focus, reverting to base method. Removed InvertSelectedCellFirstCharacter setting in FileDialog.cs.

* Add tree view toggle to FileDialog

Introduced a new `_btnTreeToggle` button in `FileDialog.cs` to manage the visibility of a tree view within the file dialog interface. Added a new localized string `fdTree` in `Strings.Designer.cs` and `Strings.resx` for UI elements related to the tree view. Adjusted the layout and visibility of the tree and table views to accommodate the toggle functionality. Implemented methods `ToggleTreeVisibility`, `SetTreeVisible`, and `GetTreeToggleText` to handle tree view visibility logic. Cleaned up code and comments for clarity.

* Update localization and test logic for FileDialog

Updated `fdSearchCaption` localization from "Enter search string" to "Find" in `Strings.Designer.cs` and `Strings.resx`. Modified `FileDialogFluentTests.cs` to reflect UI changes by updating button text and removing skip conditions. Adjusted `FileDialogTests.cs` to change `TextField` caption and commented out certain test logic related to path confirmation.

* Moved Search view to be inside the table view container for better usability.

Refactor FileDialog to use nullable reference types

Updated the `FileDialog` class to adopt nullable reference types for improved null safety, marking fields, properties, and methods as nullable where appropriate. Simplified UI component initialization, including repositioning `_tbFind` and `_spinnerView` into `_tableViewContainer` and assigning `Id` properties to `_tableViewContainer` and `_tableView`.

Refactored methods like `TryAcceptMulti` and `GetFocusedFiles` to handle nullability. Simplified `Task.Run` calls and removed unused code, such as the `GetTextField` method and `FileDialogPart` enum, in `FileDialogTests.cs`. Updated tests to directly access subviews using `Id` properties. Minor layout and property adjustments were made to improve maintainability.

* Refactor Dialog class and improve null safety

- Enabled nullable reference types in `Dialog.cs` for better null safety.
- Removed and reintroduced static configuration properties with `[ConfigurationProperty]` attributes for configurability.
- Refactored `Dialog` constructor to use `base.ShadowStyle` and improved button management with alignment logic.
- Updated `Canceled` property with a private backing field and debug assertions.
- Added null-forgiving operators (`!`) across the codebase for nullable reference type compatibility.
- Introduced new test cases to verify `Dialog` behavior, including modal mouse capture and `Canceled` property access.
- Refactored and modernized existing test cases for consistency and readability.
- Removed redundant test cases and performed general code cleanup.
- Improved code comments and debug assertions for clarity and robustness.

* Refactor DialogTests to replace null with empty array

Updated the `BeginButtonTestDialog` method call in `DialogTests.cs`
to replace the `null!` argument with an empty array `[]`. This
improves type safety and ensures explicit handling of the argument.
Tig 1 miesiąc temu
rodzic
commit
1eb5a4e1b9

+ 11 - 2
Terminal.Gui/Resources/Strings.Designer.cs

@@ -19,7 +19,7 @@ namespace Terminal.Gui.Resources {
     // class via a tool like ResGen or Visual Studio.
     // To add or remove a member, edit your .ResX file then rerun ResGen
     // with the /str option, or rebuild your VS project.
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
     internal class Strings {
@@ -682,7 +682,7 @@ namespace Terminal.Gui.Resources {
         }
         
         /// <summary>
-        ///   Looks up a localized string similar to Enter Search.
+        ///   Looks up a localized string similar to Find.
         /// </summary>
         internal static string fdSearchCaption {
             get {
@@ -717,6 +717,15 @@ namespace Terminal.Gui.Resources {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to _Tree.
+        /// </summary>
+        internal static string fdTree {
+            get {
+                return ResourceManager.GetString("fdTree", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Type.
         /// </summary>

+ 5 - 1
Terminal.Gui/Resources/Strings.resx

@@ -195,7 +195,7 @@
     <value>Enter Path</value>
   </data>
   <data name="fdSearchCaption" xml:space="preserve">
-    <value>_Find:</value>
+    <value>Find</value>
   </data>
   <data name="fdSize" xml:space="preserve">
     <value>Size</value>
@@ -355,4 +355,8 @@
   <data name="cmd.New.Help" xml:space="preserve">
     <value>New file</value>
   </data>
+  <data name="fdTree" xml:space="preserve">
+    <value>_Tree</value>
+    <comment>Show/Hide Tree View</comment>
+  </data>
 </root>

+ 65 - 96
Terminal.Gui/Views/Dialog.cs

@@ -1,4 +1,5 @@
-namespace Terminal.Gui.Views;
+#nullable enable
+namespace Terminal.Gui.Views;
 
 /// <summary>
 ///     A <see cref="Toplevel.Modal"/> <see cref="Window"/>. Supports a simple API for adding <see cref="Button"/>s
@@ -14,46 +15,6 @@
 /// </remarks>
 public class Dialog : Window
 {
-    /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
-    /// <remarks>This property can be set in a Theme.</remarks>
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End;
-
-    /// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
-    /// <remarks>This property can be set in a Theme.</remarks>
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
-
-    /// <summary>
-    ///     Defines the default minimum Dialog width, as a percentage of the container width. Can be configured via
-    ///     <see cref="ConfigurationManager"/>.
-    /// </summary>
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public static int DefaultMinimumWidth { get; set; } = 80;
-
-    /// <summary>
-    ///     Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via
-    ///     <see cref="ConfigurationManager"/>.
-    /// </summary>
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public static int DefaultMinimumHeight { get; set; } = 80;
-
-    /// <summary>
-    ///     Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
-    /// </summary>
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.Transparent;
-
-    /// <summary>
-    ///     Defines the default border styling for <see cref="Dialog"/>. Can be configured via
-    ///     <see cref="ConfigurationManager"/>.
-    /// </summary>
-
-    [ConfigurationProperty (Scope = typeof (ThemeScope))]
-    public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy;
-
-    private readonly List<Button> _buttons = new ();
-
     /// <summary>
     ///     Initializes a new instance of the <see cref="Dialog"/> class with no <see cref="Button"/>s.
     /// </summary>
@@ -67,7 +28,7 @@ public class Dialog : Window
     public Dialog ()
     {
         Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped;
-        ShadowStyle = DefaultShadow;
+        base.ShadowStyle = DefaultShadow;
         BorderStyle = DefaultBorderStyle;
 
         X = Pos.Center ();
@@ -82,45 +43,23 @@ public class Dialog : Window
         ButtonAlignmentModes = DefaultButtonAlignmentModes;
     }
 
-    // BUGBUG: We override GetNormal/FocusColor because "Dialog" Scheme is goofy.
-    // BUGBUG: By defn, a Dialog is Modal, and thus HasFocus is always true. OnDrawContent
-    // BUGBUG: Calls these methods.
-    // TODO: Fix this in https://github.com/gui-cs/Terminal.Gui/issues/2381
-
-    /// <inheritdoc/>
-    /// <inheritdoc/>
-    protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
-    {
-        if (role == VisualRole.Normal || role == VisualRole.Focus)
-        {
-            currentAttribute = GetScheme ().Normal;
-
-            return true;
-        }
-
-        return base.OnGettingAttributeForRole (role, ref currentAttribute);
-    }
+    private readonly List<Button> _buttons = [];
 
     private bool _canceled;
 
-    /// <summary>Gets a value indicating whether the <see cref="Dialog"/> was canceled.</summary>
-    /// <remarks>The default value is <see langword="true"/>.</remarks>
-    public bool Canceled
+    /// <summary>
+    ///     Adds a <see cref="Button"/> to the <see cref="Dialog"/>, its layout will be controlled by the
+    ///     <see cref="Dialog"/>
+    /// </summary>
+    /// <param name="button">Button to add.</param>
+    public void AddButton (Button button)
     {
-        get
-        {
-            return _canceled;
-        }
-        set
-        {
-#if DEBUG_IDISPOSABLE
-            if (EnableDebugIDisposableAsserts && WasDisposed)
-            {
-                throw new ObjectDisposedException (GetType ().FullName);
-            }
-#endif
-            _canceled = value;
-        }
+        // Use a distinct GroupId so users can use Pos.Align for other views in the Dialog
+        button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, GetHashCode ());
+        button.Y = Pos.AnchorEnd ();
+
+        _buttons.Add (button);
+        Add (button);
     }
 
     // TODO: Update button.X = Pos.Justify when alignment changes
@@ -138,11 +77,6 @@ public class Dialog : Window
         get => _buttons.ToArray ();
         init
         {
-            if (value is null)
-            {
-                return;
-            }
-
             foreach (Button b in value)
             {
                 AddButton (b);
@@ -150,23 +84,58 @@ public class Dialog : Window
         }
     }
 
-    /// <summary>
-    ///     Adds a <see cref="Button"/> to the <see cref="Dialog"/>, its layout will be controlled by the
-    ///     <see cref="Dialog"/>
-    /// </summary>
-    /// <param name="button">Button to add.</param>
-    public void AddButton (Button button)
+    /// <summary>Gets a value indicating whether the <see cref="Dialog"/> was canceled.</summary>
+    /// <remarks>The default value is <see langword="true"/>.</remarks>
+    public bool Canceled
     {
-        if (button is null)
+        get { return _canceled; }
+        set
         {
-            return;
+#if DEBUG_IDISPOSABLE
+            if (EnableDebugIDisposableAsserts && WasDisposed)
+            {
+                throw new ObjectDisposedException (GetType ().FullName);
+            }
+#endif
+            _canceled = value;
         }
+    }
 
-        // Use a distinct GroupId so users can use Pos.Align for other views in the Dialog
-        button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, GetHashCode ());
-        button.Y = Pos.AnchorEnd ();
+    /// <summary>
+    ///     Defines the default border styling for <see cref="Dialog"/>. Can be configured via
+    ///     <see cref="ConfigurationManager"/>.
+    /// </summary>
 
-        _buttons.Add (button);
-        Add (button);
-    }
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy;
+
+    /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
+    /// <remarks>This property can be set in a Theme.</remarks>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End;
+
+    /// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
+    /// <remarks>This property can be set in a Theme.</remarks>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
+
+    /// <summary>
+    ///     Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via
+    ///     <see cref="ConfigurationManager"/>.
+    /// </summary>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static int DefaultMinimumHeight { get; set; } = 80;
+
+    /// <summary>
+    ///     Defines the default minimum Dialog width, as a percentage of the container width. Can be configured via
+    ///     <see cref="ConfigurationManager"/>.
+    /// </summary>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static int DefaultMinimumWidth { get; set; } = 80;
+
+    /// <summary>
+    ///     Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
+    /// </summary>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.Transparent;
 }

+ 145 - 75
Terminal.Gui/Views/FileDialogs/FileDialog.cs

@@ -9,7 +9,6 @@ namespace Terminal.Gui.Views;
 /// </summary>
 public class FileDialog : Dialog, IDesignable
 {
-    private const int ALIGNMENT_GROUP_INPUT = 32;
     private const int ALIGNMENT_GROUP_COMPLETE = 55;
 
     /// <summary>Gets the Path separators for the operating system</summary>
@@ -35,6 +34,7 @@ public class FileDialog : Dialog, IDesignable
     private readonly Button _btnForward;
     private readonly Button _btnOk;
     private readonly Button _btnUp;
+    private readonly Button _btnTreeToggle;
     private readonly IFileSystem? _fileSystem;
     private readonly FileDialogHistory _history;
     private readonly SpinnerView _spinnerView;
@@ -43,13 +43,13 @@ public class FileDialog : Dialog, IDesignable
     private readonly TextField _tbFind;
     private readonly TextField _tbPath;
     private readonly TreeView<IFileSystemInfo> _treeView;
-    private MenuBarItem _allowedTypeMenu;
-    private MenuBar _allowedTypeMenuBar;
-    private MenuItem [] _allowedTypeMenuItems;
+    private MenuBarItem? _allowedTypeMenu;
+    private MenuBar? _allowedTypeMenuBar;
+    private MenuItem []? _allowedTypeMenuItems;
     private int _currentSortColumn;
     private bool _currentSortIsAsc = true;
     private bool _disposed;
-    private string _feedback;
+    private string? _feedback;
     private bool _loaded;
 
     private bool _pushingState;
@@ -86,6 +86,7 @@ public class FileDialog : Dialog, IDesignable
                                 }
 
                                 Accept (true);
+                                e.Handled = true;
                             };
 
         _btnCancel = new ()
@@ -110,6 +111,19 @@ public class FileDialog : Dialog, IDesignable
                                     }
                                 };
 
+        // Tree toggle button - shares alignment group with OK/Cancel
+        _btnTreeToggle = new ()
+        {
+            X = 0,//Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_COMPLETE),
+            Y = Pos.AnchorEnd (),
+            NoPadding = true
+        };
+        _btnTreeToggle.Accepting += (s, e) =>
+        {
+            e.Handled = true;
+            ToggleTreeVisibility ();
+        };
+
         _btnUp = new () { X = 0, Y = 1, NoPadding = true };
         _btnUp.Text = GetUpButtonText ();
         _btnUp.Accepting += (s, e) =>
@@ -134,7 +148,7 @@ public class FileDialog : Dialog, IDesignable
                                      e.Handled = true;
                                  };
 
-        _tbPath = new () { Width = Dim.Fill (), CaptionColor = new (Color.Black) };
+        _tbPath = new () { Width = Dim.Fill (),/* CaptionColor = new (Color.Black)*/ };
 
         _tbPath.KeyDown += (s, k) =>
                            {
@@ -149,13 +163,13 @@ public class FileDialog : Dialog, IDesignable
         _tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
 
         // Create tree view container (left pane)
-        View treeViewContainer = new ()
+        _treeView = new ()
         {
-            X = -1,
+            X = 0,
             Y = Pos.Bottom (_btnBack),
-            Width = Dim.Fill (Dim.Func (_ => IsInitialized ? _tableViewContainer!.Frame.Width - 1 : 1)),
+            Width = Dim.Fill (Dim.Func (_ => IsInitialized ? _tableViewContainer!.Frame.Width - 30 : 30)),
             Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)),
-            CanFocus = true,
+            Visible = false
         };
 
         // Create table view container (right pane)
@@ -168,20 +182,20 @@ public class FileDialog : Dialog, IDesignable
             Arrangement = ViewArrangement.LeftResizable,
             BorderStyle = LineStyle.Dashed,
             SuperViewRendersLineCanvas = true,
-            CanFocus = true
+            CanFocus = true,
+            Id = "_tableViewContainer"
         };
-        _tableViewContainer.Border!.Thickness = new (1, 0, 0, 0);
 
         _tableView = new ()
         {
             Width = Dim.Fill (),
-            Height = Dim.Fill (),
+            Height = Dim.Fill (1),
             FullRowSelect = true,
+            Id = "_tableView"
         };
         _tableView.CollectionNavigator = new FileDialogCollectionNavigator (this, _tableView);
         _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select);
         _tableView.MouseClick += OnTableViewMouseClick;
-        _tableView.Style.InvertSelectedCellFirstCharacter = true;
         Style.TableStyle = _tableView.Style;
 
         ColumnStyle nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
@@ -200,8 +214,6 @@ public class FileDialog : Dialog, IDesignable
         typeStyle.MinWidth = 6;
         typeStyle.ColorGetter = ColorGetter;
 
-        _treeView = new () { Width = Dim.Fill (), Height = Dim.Fill () };
-
         var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
         _treeView.TreeBuilder = fileDialogTreeBuilder;
         _treeView.AspectGetter = AspectGetter;
@@ -209,20 +221,44 @@ public class FileDialog : Dialog, IDesignable
 
         _treeView.SelectionChanged += TreeView_SelectionChanged;
 
-        treeViewContainer.Add (_treeView);
         _tableViewContainer.Add (_tableView);
 
+        _tableView.Style.ShowHorizontalHeaderOverline = true;
+        _tableView.Style.ShowVerticalCellLines = true;
+        _tableView.Style.ShowVerticalHeaderLines = true;
+        _tableView.Style.AlwaysShowHeaders = true;
+        _tableView.Style.ShowHorizontalHeaderUnderline = true;
+        _tableView.Style.ShowHorizontalScrollIndicators = true;
+
+        _history = new (this);
+
+        _tbPath.TextChanged += (s, e) => PathChanged ();
+
+        _tableView.CellActivated += CellActivate;
+        _tableView.KeyDown += (s, k) => k.Handled = TableView_KeyUp (k);
+        _tableView.SelectedCellChanged += TableView_SelectedCellChanged;
+
+        _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.Start);
+        _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End);
+        _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend);
+        _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend);
+
         _tbFind = new ()
         {
-            X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_INPUT),
-            CaptionColor = new (Color.Black),
-            Width = 30,
-            Y = Pos.Top (_btnOk),
-            HotKey = Key.F.WithAlt
+            X = 0,
+            Width = Dim.Fill (),
+            Y = Pos.AnchorEnd (),
+            HotKey = Key.F.WithAlt,
+            Id = "_tbFind",
         };
 
         _spinnerView = new ()
-        { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_INPUT), Y = Pos.AnchorEnd (1), Visible = false };
+        {
+            // The spinner view is positioned over the last column of _tbFind
+            X = Pos.Right (_tbFind) - 1,
+            Y = Pos.Top (_tbFind),
+            Visible = false
+        };
 
         _tbFind.TextChanged += (s, o) => RestartSearch ();
 
@@ -242,27 +278,6 @@ public class FileDialog : Dialog, IDesignable
                                    }
                                }
                            };
-
-        _tableView.Style.ShowHorizontalHeaderOverline = true;
-        _tableView.Style.ShowVerticalCellLines = true;
-        _tableView.Style.ShowVerticalHeaderLines = true;
-        _tableView.Style.AlwaysShowHeaders = true;
-        _tableView.Style.ShowHorizontalHeaderUnderline = true;
-        _tableView.Style.ShowHorizontalScrollIndicators = true;
-
-        _history = new (this);
-
-        _tbPath.TextChanged += (s, e) => PathChanged ();
-
-        _tableView.CellActivated += CellActivate;
-        _tableView.KeyDown += (s, k) => k.Handled = TableView_KeyUp (k);
-        _tableView.SelectedCellChanged += TableView_SelectedCellChanged;
-
-        _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.Start);
-        _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End);
-        _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend);
-        _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend);
-
         AllowsMultipleSelection = false;
 
         UpdateNavigationVisibility ();
@@ -271,13 +286,18 @@ public class FileDialog : Dialog, IDesignable
         base.Add (_btnUp);
         base.Add (_btnBack);
         base.Add (_btnForward);
-        base.Add (treeViewContainer);
+        base.Add (_treeView);
         base.Add (_tableViewContainer);
-        base.Add (_tbFind);
-        base.Add (_spinnerView);
+        _tableViewContainer.Add (_tbFind);
+        _tableViewContainer.Add (_spinnerView);
 
+        // Add the toggle along with OK/Cancel so they align as a group
+        base.Add (_btnTreeToggle);
         base.Add (_btnOk);
         base.Add (_btnCancel);
+
+        // Default: Tree hidden and splitter hidden
+        SetTreeVisible (false);
     }
 
     /// <summary>
@@ -301,7 +321,7 @@ public class FileDialog : Dialog, IDesignable
     }
 
     /// <summary>The UI selected <see cref="IAllowedType"/> from combo box. May be null.</summary>
-    public IAllowedType CurrentFilter { get; private set; }
+    public IAllowedType? CurrentFilter { get; private set; }
 
     /// <summary>
     ///     Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts to delete a selected file(s). Set
@@ -364,13 +384,13 @@ public class FileDialog : Dialog, IDesignable
     public FileDialogStyle Style { get; }
 
     /// <summary>Gets the currently open directory and known children presented in the dialog.</summary>
-    internal FileDialogState State { get; private set; }
+    internal FileDialogState? State { get; private set; }
 
     /// <summary>
     ///     Event fired when user attempts to confirm a selection (or multi selection). Allows you to cancel the selection
     ///     or undertake alternative behavior e.g. open a dialog "File already exists, Overwrite? yes/no".
     /// </summary>
-    public event EventHandler<FilesSelectedEventArgs> FilesSelected;
+    public event EventHandler<FilesSelectedEventArgs>? FilesSelected;
 
     /// <summary>
     ///     Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees that <paramref name="file"/>
@@ -425,6 +445,8 @@ public class FileDialog : Dialog, IDesignable
             return;
         }
 
+        Arrangement |= ViewArrangement.Resizable;
+
         _loaded = true;
 
         // May have been updated after instance was constructed
@@ -515,6 +537,9 @@ public class FileDialog : Dialog, IDesignable
             MoveSubViewTowardsStart (_btnCancel);
         }
 
+        // Ensure toggle button text matches current state after sizing
+        SetTreeVisible (false);
+
         SetNeedsDraw ();
         SetNeedsLayout ();
     }
@@ -558,7 +583,7 @@ public class FileDialog : Dialog, IDesignable
 
     internal void ApplySort ()
     {
-        FileSystemInfoStats [] stats = State?.Children ?? new FileSystemInfoStats [0];
+        FileSystemInfoStats [] stats = State?.Children ?? [];
 
         // This portion is never reordered (always .. at top then folders)
         IOrderedEnumerable<FileSystemInfoStats> forcedOrder = stats
@@ -577,7 +602,7 @@ public class FileDialog : Dialog, IDesignable
                                                     FileDialogTableSource.GetRawColumnValue (_currentSortColumn, f)
                                                );
 
-        State.Children = ordered.ToArray ();
+        State!.Children = ordered.ToArray ();
 
         _tableView.Update ();
     }
@@ -620,7 +645,7 @@ public class FileDialog : Dialog, IDesignable
     /// <param name="toRestore"></param>
     internal void RestoreSelection (IFileSystemInfo toRestore)
     {
-        _tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
+        _tableView.SelectedRow = State!.Children.IndexOf (r => r.FileSystemInfo == toRestore);
         _tableView.EnsureSelectedCellIsVisible ();
     }
 
@@ -709,17 +734,17 @@ public class FileDialog : Dialog, IDesignable
 
         for (var i = 0; i < AllowedTypes.Count; i++)
         {
-            _allowedTypeMenuItems [i].Checked = i == idx;
+            _allowedTypeMenuItems! [i].Checked = i == idx;
         }
 
-        _allowedTypeMenu.Title = allow.ToString ()!;
+        _allowedTypeMenu!.Title = allow.ToString ()!;
 
         CurrentFilter = allow;
 
         _tbPath.ClearAllSelection ();
         _tbPath.Autocomplete.ClearSuggestions ();
 
-        State.RefreshChildren ();
+        State!.RefreshChildren ();
         WriteStateToTableView ();
     }
 
@@ -815,7 +840,7 @@ public class FileDialog : Dialog, IDesignable
 
     private void Delete ()
     {
-        IFileSystemInfo [] toDelete = GetFocusedFiles ();
+        IFileSystemInfo [] toDelete = GetFocusedFiles ()!;
 
         if (FileOperationsHandler.Delete (toDelete))
         {
@@ -853,9 +878,9 @@ public class FileDialog : Dialog, IDesignable
 
     private string GetBackButtonText () { return Glyphs.LeftArrow + "-"; }
 
-    private IFileSystemInfo [] GetFocusedFiles ()
+    private IFileSystemInfo []? GetFocusedFiles ()
     {
-        if (!_tableView.HasFocus || !_tableView.CanFocus || FileOperationsHandler is null)
+        if (!_tableView.HasFocus || !_tableView.CanFocus)
         {
             return null;
         }
@@ -915,7 +940,7 @@ public class FileDialog : Dialog, IDesignable
 
     private bool IsCompatibleWithOpenMode (string s, out string reason)
     {
-        reason = null;
+        reason = string.Empty;
 
         if (string.IsNullOrWhiteSpace (s))
         {
@@ -1007,7 +1032,7 @@ public class FileDialog : Dialog, IDesignable
     private void New ()
     {
         {
-            IFileSystemInfo created = FileOperationsHandler.New (_fileSystem, State.Directory);
+            IFileSystemInfo created = FileOperationsHandler.New (_fileSystem, State!.Directory);
 
             if (created is { })
             {
@@ -1138,13 +1163,13 @@ public class FileDialog : Dialog, IDesignable
 
     private void RefreshState ()
     {
-        State.RefreshChildren ();
+        State!.RefreshChildren ();
         PushState (State, false, false, false);
     }
 
     private void Rename ()
     {
-        IFileSystemInfo [] toRename = GetFocusedFiles ();
+        IFileSystemInfo [] toRename = GetFocusedFiles ()!;
 
         if (toRename?.Length == 1)
         {
@@ -1328,7 +1353,7 @@ public class FileDialog : Dialog, IDesignable
 
         if (stats.IsParent)
         {
-            dest = State.Directory;
+            dest = State!.Directory;
         }
         else
         {
@@ -1340,7 +1365,7 @@ public class FileDialog : Dialog, IDesignable
             _pushingState = true;
 
             SetPathToSelectedObject (dest);
-            State.Selected = stats;
+            State!.Selected = stats;
             _tbPath.Autocomplete.ClearSuggestions ();
         }
         finally
@@ -1363,7 +1388,7 @@ public class FileDialog : Dialog, IDesignable
 
         if (selected is IDirectoryInfo && Style.PreserveFilenameOnDirectoryChanges)
         {
-            if (!string.IsNullOrWhiteSpace (Path) && !_fileSystem.Directory.Exists (Path))
+            if (!string.IsNullOrWhiteSpace (Path) && !_fileSystem!.Directory.Exists (Path))
             {
                 var currentFile = _fileSystem.Path.GetFileName (Path);
 
@@ -1384,19 +1409,21 @@ public class FileDialog : Dialog, IDesignable
         IEnumerable<FileSystemInfoStats> multi = MultiRowToStats ();
         string? reason = null;
 
-        if (!multi.Any ())
+        IEnumerable<FileSystemInfoStats> fileSystemInfoStatsEnumerable = multi as FileSystemInfoStats [] ?? multi.ToArray ();
+
+        if (!fileSystemInfoStatsEnumerable.Any ())
         {
             return false;
         }
 
-        if (multi.All (
-                       m => IsCompatibleWithOpenMode (
-                                                      m.FileSystemInfo.FullName,
-                                                      out reason
-                                                     )
-                      ))
+        if (fileSystemInfoStatsEnumerable.All (
+                                               m => IsCompatibleWithOpenMode (
+                                                                              m.FileSystemInfo.FullName,
+                                                                              out reason
+                                                                             )
+                                              ))
         {
-            Accept (multi);
+            Accept (fileSystemInfoStatsEnumerable);
 
             return true;
         }
@@ -1426,6 +1453,49 @@ public class FileDialog : Dialog, IDesignable
         _tableView.Update ();
     }
 
+    // --- Tree visibility management ---
+
+    private void ToggleTreeVisibility ()
+    {
+        SetTreeVisible (!_treeView.Visible);
+    }
+
+    private void SetTreeVisible (bool visible)
+    {
+        _treeView.Enabled = visible;
+        _treeView.Visible = visible;
+
+        if (visible)
+        {
+            // When visible, the table view's left edge is a splitter next to the tree
+            _treeView.Width = Dim.Fill (Dim.Func (_ => IsInitialized ? _tableViewContainer!.Frame.Width - 30 : 30));
+            _tableViewContainer.X = 30;
+            _tableViewContainer.Arrangement = ViewArrangement.LeftResizable;
+            _tableViewContainer.Border!.Thickness = new (1, 0, 0, 0);
+        }
+        else
+        {
+            // When hidden, table occupies full width and splitter is hidden/disabled
+            _treeView.Width = 0;
+            _tableViewContainer.X = 0;
+            _tableViewContainer.Width = Dim.Fill ();
+            _tableViewContainer.Arrangement = ViewArrangement.Fixed;
+            _tableViewContainer.Border!.Thickness = new (0, 0, 0, 0);
+        }
+        _btnTreeToggle.Text = GetTreeToggleText (visible);
+
+        SetNeedsLayout ();
+        SetNeedsDraw ();
+    }
+
+    private string GetTreeToggleText (bool visible)
+    {
+        return visible
+                   ? $"{Glyphs.LeftArrow}{Strings.fdTree}"
+                   : $"{Glyphs.RightArrow}{Strings.fdTree}";
+
+    }
+
     /// <summary>State representing a recursive search from <see cref="FileDialogState.Directory"/> downwards.</summary>
     internal class SearchState : FileDialogState
     {
@@ -1470,7 +1540,7 @@ public class FileDialog : Dialog, IDesignable
                       }
                      );
 
-            Task.Run (() => { UpdateChildren (); });
+            Task.Run (UpdateChildren);
         }
 
         private void RecursiveFind (IDirectoryInfo directory)

+ 60 - 60
Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs

@@ -74,7 +74,7 @@ public class FileDialogFluentTests
         using var c = With.A (() => NewSaveDialog (out sd,modal:false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .Focus<Button> (b => b.Text == "_Cancel")
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Enter ()
                           .Stop ();
     }
@@ -88,7 +88,7 @@ public class FileDialogFluentTests
                           .ScreenShot ("Save dialog", _out)
                           .LeftClick<Button> (b => b.Text == "_Cancel")
                           .WriteOutLogs (_out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Stop ();
     }
     [Theory]
@@ -100,7 +100,7 @@ public class FileDialogFluentTests
                           .ScreenShot ("Save dialog", _out)
                           .Send (Key.C.WithAlt)
                           .WriteOutLogs (_out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Stop ();
     }
 
@@ -115,8 +115,8 @@ public class FileDialogFluentTests
                           .LeftClick<Button> (b => b.Text == "_Save")
                           .WaitIteration ()
                           .WriteOutLogs (_out)
-                          .AssertFalse(sd.Canceled)
-                          .AssertEqual (GetFileSystemRoot (fs), sd.FileName)
+                          .AssertFalse(sd!.Canceled)
+                          .AssertEqual (GetFileSystemRoot (fs!), sd!.FileName)
                           .Stop ();
     }
 
@@ -130,8 +130,8 @@ public class FileDialogFluentTests
                           .ScreenShot ("Save dialog", _out)
                           .Send (Key.S.WithAlt)
                           .WriteOutLogs (_out)
-                          .AssertFalse (sd.Canceled)
-                          .AssertEqual (GetFileSystemRoot (fs), sd.FileName)
+                          .AssertFalse (sd!.Canceled)
+                          .AssertEqual (GetFileSystemRoot (fs!), sd!.FileName)
                           .Stop ();
 
     }
@@ -147,8 +147,8 @@ public class FileDialogFluentTests
                           .Focus<Button> (b => b.Text == "_Save")
                           .Enter ()
                           .WriteOutLogs (_out)
-                          .AssertFalse(sd.Canceled)
-                          .AssertEqual (GetFileSystemRoot(fs), sd.FileName)
+                          .AssertFalse(sd!.Canceled)
+                          .AssertEqual (GetFileSystemRoot(fs!), sd!.FileName)
                           .Stop ();
     }
 
@@ -159,7 +159,7 @@ public class FileDialogFluentTests
                 "/";
     }
 
-    [Theory (Skip = "New splitter design removes expand button.")]
+    [Theory]
     [ClassData (typeof (TestDrivers))]
     public void SaveFileDialog_PressingPopTree_ShouldNotChangeCancel (TestDriver d)
     {
@@ -167,17 +167,17 @@ public class FileDialogFluentTests
         MockFileSystem? fs = null;
         using var c = With.A (() => NewSaveDialog (out sd, out fs,modal:false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
-                          .AssertTrue (sd.Canceled)
-                          .Focus<Button> (b => b.Text == "►")
+                          .AssertTrue (sd!.Canceled)
+                          .Focus<Button> (b => b.Text == "►_Tree")
                           .Enter ()
                           .ScreenShot ("After pop tree", _out)
                           .WriteOutLogs (_out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Stop ();
 
     }
 
-    [Theory (Skip = "New splitter design removes expand button.")]
+    [Theory]
     [ClassData (typeof (TestDrivers))]
     public void SaveFileDialog_PopTree_AndNavigate (TestDriver d)
     {
@@ -185,8 +185,8 @@ public class FileDialogFluentTests
         MockFileSystem? fs = null;
         using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
-                          .AssertTrue (sd.Canceled)
-                          .LeftClick<Button> (b => b.Text == "►")
+                          .AssertTrue (sd!.Canceled)
+                          .LeftClick<Button> (b => b.Text == "►_Tree")
                           .ScreenShot ("After pop tree", _out)
                           .Focus<TreeView<IFileSystemInfo>> (_ => true)
                           .Right ()
@@ -195,8 +195,8 @@ public class FileDialogFluentTests
                           .ScreenShot ("After navigate down in tree", _out)
                           .Enter ()
                           .WaitIteration ()
-                          .AssertFalse (sd.Canceled)
-                          .AssertContains ("empty-dir", sd.FileName)
+                          .AssertFalse (sd!.Canceled)
+                          .AssertContains ("empty-dir", sd!.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
     }
@@ -208,13 +208,13 @@ public class FileDialogFluentTests
         SaveDialog? sd = null;
         MockFileSystem? fs = null;
         using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
-                          .Then (()=>sd.Style.PreserveFilenameOnDirectoryChanges=true)
+                          .Then (()=>sd!.Style.PreserveFilenameOnDirectoryChanges=true)
                           .ScreenShot ("Save dialog", _out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Focus<TextField> (_=>true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
-                          .AssertIsType <TextField>(sd.Focused)
+                          .AssertIsType <TextField>(sd!.Focused)
                           // Type a filename into the dialog
                           .RaiseKeyDownEvent (Key.H)
                           .RaiseKeyDownEvent (Key.E)
@@ -223,23 +223,23 @@ public class FileDialogFluentTests
                           .RaiseKeyDownEvent (Key.O)
                           .WaitIteration ()
                           .ScreenShot ("After typing filename 'hello'", _out)
-                          .AssertEndsWith ("hello", sd.Path)
-                          //.LeftClick<Button> (b => b.Text == "►►")
-                          //.ScreenShot ("After pop tree", _out)
+                          .AssertEndsWith ("hello", sd!.Path)
+                          .LeftClick<Button> (b => b.Text == "►_Tree")
+                          .ScreenShot ("After pop tree", _out)
                           .Focus<TreeView<IFileSystemInfo>> (_ => true)
                           .Right ()
                           .ScreenShot ("After expand tree", _out)
                           // Because of PreserveFilenameOnDirectoryChanges we should select the new dir but keep the filename
-                          .AssertEndsWith ("hello", sd.Path)
+                          .AssertEndsWith ("hello", sd!.Path)
                           .Down ()
                           .ScreenShot ("After navigate down in tree", _out)
                           // Because of PreserveFilenameOnDirectoryChanges we should select the new dir but keep the filename
-                          .AssertContains ("empty-dir",sd.Path)
-                          .AssertEndsWith ("hello", sd.Path)
+                          .AssertContains ("empty-dir",sd!.Path)
+                          .AssertEndsWith ("hello", sd!.Path)
                           .Enter ()
                           .WaitIteration ()
-                          .AssertFalse (sd.Canceled)
-                          .AssertContains ("empty-dir", sd.FileName)
+                          .AssertFalse (sd!.Canceled)
+                          .AssertContains ("empty-dir", sd!.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
     }
@@ -251,13 +251,13 @@ public class FileDialogFluentTests
         SaveDialog? sd = null;
         MockFileSystem? fs = null;
         using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
-                          .Then (()=> sd.Style.PreserveFilenameOnDirectoryChanges = false)
+                          .Then (()=> sd!.Style.PreserveFilenameOnDirectoryChanges = false)
                           .ScreenShot ("Save dialog", _out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Focus<TextField> (_ => true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
-                          .AssertIsType<TextField> (sd.Focused)
+                          .AssertIsType<TextField> (sd!.Focused)
                           // Type a filename into the dialog
                           .RaiseKeyDownEvent (Key.H)
                           .RaiseKeyDownEvent (Key.E)
@@ -266,21 +266,21 @@ public class FileDialogFluentTests
                           .RaiseKeyDownEvent (Key.O)
                           .WaitIteration ()
                           .ScreenShot ("After typing filename 'hello'", _out)
-                          .AssertEndsWith ("hello", sd.Path)
-                          //.LeftClick<Button> (b => b.Text == "►►")
-                          //.ScreenShot ("After pop tree", _out)
+                          .AssertEndsWith ("hello", sd!.Path)
+                          .LeftClick<Button> (b => b.Text == "►_Tree")
+                          .ScreenShot ("After pop tree", _out)
                           .Focus<TreeView<IFileSystemInfo>> (_ => true)
                           .Right ()
                           .ScreenShot ("After expand tree", _out)
                           .Down ()
                           .ScreenShot ("After navigate down in tree", _out)
                           // PreserveFilenameOnDirectoryChanges is false so just select new path
-                          .AssertEndsWith ("empty-dir", sd.Path)
-                          .AssertDoesNotContain ("hello", sd.Path)
+                          .AssertEndsWith ("empty-dir", sd!.Path)
+                          .AssertDoesNotContain ("hello", sd!.Path)
                           .Enter ()
                           .WaitIteration ()
-                          .AssertFalse (sd.Canceled)
-                          .AssertContains ("empty-dir", sd.FileName)
+                          .AssertFalse (sd!.Canceled)
+                          .AssertContains ("empty-dir", sd!.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
     }
@@ -292,13 +292,13 @@ public class FileDialogFluentTests
         SaveDialog? sd = null;
         MockFileSystem? fs = null;
         using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
-                          .Then (() => sd.Style.PreserveFilenameOnDirectoryChanges = preserve)
+                          .Then (() => sd!.Style.PreserveFilenameOnDirectoryChanges = preserve)
                           .ScreenShot ("Save dialog", _out)
-                          .AssertTrue (sd.Canceled)
+                          .AssertTrue (sd!.Canceled)
                           .Focus<TextField> (_ => true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
-                          .AssertIsType<TextField> (sd.Focused)
+                          .AssertIsType<TextField> (sd!.Focused)
                           // Type a filename into the dialog
                           .RaiseKeyDownEvent (Key.H)
                           .RaiseKeyDownEvent (Key.E)
@@ -307,7 +307,7 @@ public class FileDialogFluentTests
                           .RaiseKeyDownEvent (Key.O)
                           .WaitIteration ()
                           .ScreenShot ("After typing filename 'hello'", _out)
-                          .AssertEndsWith ("hello", sd.Path)
+                          .AssertEndsWith ("hello", sd!.Path)
                           .Focus<TableView> (_ => true)
                           .ScreenShot ("After focus table", _out)
                           .Down ()
@@ -315,13 +315,13 @@ public class FileDialogFluentTests
 
         if (preserve)
         {
-            c.AssertContains ("logs", sd.Path)
-             .AssertEndsWith ("hello", sd.Path);
+            c.AssertContains ("logs", sd!.Path)
+             .AssertEndsWith ("hello", sd!.Path);
         }
         else
         {
-            c.AssertContains ("logs", sd.Path)
-             .AssertDoesNotContain ("hello", sd.Path);
+            c.AssertContains ("logs", sd!.Path)
+             .AssertDoesNotContain ("hello", sd!.Path);
         }
 
         c.Up ()
@@ -329,13 +329,13 @@ public class FileDialogFluentTests
 
         if (preserve)
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertEndsWith ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertEndsWith ("hello", sd!.Path);
         }
         else
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertDoesNotContain ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertDoesNotContain ("hello", sd!.Path);
         }
 
         c.Enter ()
@@ -344,28 +344,28 @@ public class FileDialogFluentTests
 
         if (preserve)
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertEndsWith ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertEndsWith ("hello", sd!.Path);
         }
         else
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertDoesNotContain ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertDoesNotContain ("hello", sd!.Path);
         }
 
         c.LeftClick<Button> (b => b.Text == "_Save");
         c.WaitIteration ();
-        c.AssertFalse (sd.Canceled);
+        c.AssertFalse (sd!.Canceled);
 
         if (preserve)
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertEndsWith ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertEndsWith ("hello", sd!.Path);
         }
         else
         {
-            c.AssertContains ("empty-dir", sd.Path)
-             .AssertDoesNotContain ("hello", sd.Path);
+            c.AssertContains ("empty-dir", sd!.Path)
+             .AssertDoesNotContain ("hello", sd!.Path);
         }
 
         c.WriteOutLogs (_out);

+ 178 - 184
Tests/UnitTests/Dialogs/DialogTests.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using UnitTests;
 using Xunit.Abstractions;
 using static Terminal.Gui.App.Application;
 
@@ -21,7 +20,7 @@ public class DialogTests (ITestOutputHelper output)
 
         // We test with one button first, but do this to get the width right for 2
         int width = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}".Length;
-        AutoInitShutdownAttribute.FakeResize(new(width, 1));
+        AutoInitShutdownAttribute.FakeResize (new (width, 1));
 
         // Override CM
         Dialog.DefaultButtonAlignment = Alignment.Center;
@@ -164,7 +163,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $"{Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new (buttonRow.Length, 3));
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 3));
 
         // Default - Center
         (runState, Dialog dlg) = BeginButtonTestDialog (
@@ -256,7 +255,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = string.Empty;
 
         var width = 30;
-        AutoInitShutdownAttribute.FakeResize(new(width, 1));
+        AutoInitShutdownAttribute.FakeResize (new (width, 1));
 
         // Default - Center
         buttonRow =
@@ -447,7 +446,7 @@ public class DialogTests (ITestOutputHelper output)
         //                         123456                           123456
         var buttonRow = $"{Glyphs.VLine}      {btn1} {btn2} {btn3} {btn4}      {Glyphs.VLine}";
         int width = buttonRow.GetColumns ();
-        AutoInitShutdownAttribute.FakeResize(new(width, 3));
+        AutoInitShutdownAttribute.FakeResize (new (width, 3));
 
         // Default - Center
         (runState, Dialog dlg) = BeginButtonTestDialog (
@@ -532,7 +531,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}  {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}  {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new(width, 1));
+        AutoInitShutdownAttribute.FakeResize (new (width, 1));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -596,7 +595,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}   {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}   {Glyphs.VLine}";
         width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new(width, 1));
+        AutoInitShutdownAttribute.FakeResize (new (width, 1));
 
         (runState, dlg) = BeginButtonTestDialog (
                                                  title,
@@ -676,7 +675,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {btn3} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 3));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -759,7 +758,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 3));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -823,7 +822,6 @@ public class DialogTests (ITestOutputHelper output)
     public void ButtonAlignment_Two_Hidden ()
     {
         RunState? runState = null;
-        var firstIteration = false;
 
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
@@ -839,15 +837,12 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
-
-        Dialog dlg = null;
-        Button button1, button2;
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 3));
 
         // Default (Center)
-        button1 = new () { Text = btn1Text };
-        button2 = new () { Text = btn2Text };
-        (runState, dlg) = BeginButtonTestDialog (title, width, Alignment.Center, button1, button2);
+        Button button1 = new () { Text = btn1Text };
+        Button button2 = new () { Text = btn2Text };
+        (runState, Dialog dlg) = BeginButtonTestDialog (title, width, Alignment.Center, button1, button2);
         button1.Visible = false;
         AutoInitShutdownAttribute.RunIteration ();
 
@@ -896,11 +891,73 @@ public class DialogTests (ITestOutputHelper output)
         dlg.Dispose ();
     }
 
+    [Fact]
+    [AutoInitShutdown]
+    public void Can_Access_Cancel_Property_After_Run ()
+    {
+        Dialog dlg = new ();
+
+        dlg.Ready += Dlg_Ready;
+
+        Run (dlg);
+
+#if DEBUG_IDISPOSABLE
+        Assert.False (dlg.WasDisposed);
+        Assert.False (Top!.WasDisposed);
+        Assert.Equal (dlg, Top);
+#endif
+
+        Assert.True (dlg.Canceled);
+
+        // Run it again is possible because it isn't disposed yet
+        Run (dlg);
+
+        // Run another view without dispose the prior will throw an assertion
+#if DEBUG_IDISPOSABLE
+        Dialog dlg2 = new ();
+        dlg2.Ready += Dlg_Ready;
+
+        //   Exception exception = Record.Exception (() => Run (dlg2));
+        //     Assert.NotNull (exception);
+
+        dlg.Dispose ();
+
+        // Now it's possible to tun dlg2 without throw
+        Run (dlg2);
+
+        Assert.True (dlg.WasDisposed);
+        Assert.False (Top.WasDisposed);
+        Assert.Equal (dlg2, Top);
+        Assert.False (dlg2.WasDisposed);
+
+        dlg2.Dispose ();
+
+        // tznind REMOVED: Why wouldn't you be able to read cancelled after dispose - that makes no sense
+        // Now an assertion will throw accessing the Canceled property
+        //var exception = Record.Exception (() => Assert.True (dlg.Canceled))!;
+        //Assert.NotNull (exception);
+        //Assert.StartsWith ("Cannot access a disposed object.", exception.Message);
+
+        Assert.True (Top.WasDisposed);
+        Shutdown ();
+        Assert.True (dlg2.WasDisposed);
+        Assert.Null (Top);
+#endif
+
+        return;
+
+        void Dlg_Ready (object? sender, EventArgs e)
+        {
+            ((Dialog)sender!).Canceled = true;
+            RequestStop ();
+        }
+    }
+
     [Fact]
     [AutoInitShutdown]
     public void Dialog_In_Window_With_Size_One_Button_Aligns ()
     {
-        AutoInitShutdownAttribute.FakeResize(new(20, 5));
+        AutoInitShutdownAttribute.FakeResize (new (20, 5));
 
         // Override CM
         Window.DefaultBorderStyle = LineStyle.Single;
@@ -1005,7 +1062,7 @@ public class DialogTests (ITestOutputHelper output)
                 )]
     public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height, string expected)
     {
-        AutoInitShutdownAttribute.FakeResize(new (20, height));
+        AutoInitShutdownAttribute.FakeResize (new (20, height));
         var win = new Window ();
 
         int iterations = -1;
@@ -1052,7 +1109,7 @@ public class DialogTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void Dialog_Opened_From_Another_Dialog ()
     {
-        AutoInitShutdownAttribute.FakeResize(new (30, 10));
+        AutoInitShutdownAttribute.FakeResize (new (30, 10));
 
         // Override CM
         Dialog.DefaultButtonAlignment = Alignment.Center;
@@ -1194,7 +1251,7 @@ public class DialogTests (ITestOutputHelper output)
             Height = Dim.Percent (85)
         };
         Begin (d);
-        AutoInitShutdownAttribute.FakeResize(new(100, 100));
+        AutoInitShutdownAttribute.FakeResize (new (100, 100));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         var expected = 7;
@@ -1208,7 +1265,7 @@ public class DialogTests (ITestOutputHelper output)
     {
         var d = new Dialog { X = 1, Y = 1 };
         Begin (d);
-        AutoInitShutdownAttribute.FakeResize(new(100, 100));
+        AutoInitShutdownAttribute.FakeResize (new (100, 100));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         var expected = 1;
@@ -1230,7 +1287,7 @@ public class DialogTests (ITestOutputHelper output)
         var expected = 5;
         var d = new Dialog { X = expected, Y = expected, Height = 5, Width = 5 };
         Begin (d);
-        AutoInitShutdownAttribute.FakeResize(new(20, 10));
+        AutoInitShutdownAttribute.FakeResize (new (20, 10));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         Assert.Equal (new (expected, expected), d.Frame.Location);
@@ -1247,12 +1304,63 @@ public class DialogTests (ITestOutputHelper output)
         d.Dispose ();
     }
 
+    [Fact]
+    [AutoInitShutdown]
+    public void Modal_Captures_All_Mouse ()
+    {
+        var top = new Toplevel
+        {
+            Id = "top"
+        };
+
+        var d = new Dialog
+        {
+            Width = 10,
+            Height = 10,
+            X = 1,
+            Y = 1
+        };
+
+        AutoInitShutdownAttribute.FakeResize (new (20, 20));
+
+        var iterations = 0;
+
+        Iteration += (s, a) =>
+                     {
+                         if (++iterations > 2)
+                         {
+                             RequestStop ();
+                         }
+
+                         if (iterations == 1)
+                         {
+                             Run (d);
+                             d.Dispose ();
+                         }
+                         else if (iterations == 2)
+                         {
+                             // Mouse click outside of dialog
+                             RaiseMouseEvent (new() { Flags = MouseFlags.Button1Clicked, ScreenPosition = new (0, 0) });
+                         }
+                     };
+
+        top.MouseEvent += (s, e) =>
+                          {
+                              // This should not be called because the dialog is modal
+                              Assert.Fail ("Mouse event should not be captured by the top level when a dialog is modal.");
+                          };
+
+        Run (top);
+        top.Dispose ();
+        Shutdown ();
+    }
+
     [Fact]
     [AutoInitShutdown]
     public void One_Button_Works ()
     {
         RunState? runState = null;
-        
+
         Button.DefaultShadow = ShadowStyle.None;
 
         var title = "";
@@ -1262,7 +1370,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}   {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}   {Glyphs.VLine}";
 
         int width = buttonRow.Length;
-        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 10));
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 10));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -1275,6 +1383,47 @@ public class DialogTests (ITestOutputHelper output)
         dlg.Dispose ();
     }
 
+    [Fact]
+    [AutoInitShutdown]
+    public void Run_Does_Not_Dispose_Dialog ()
+    {
+        var top = new Toplevel ();
+
+        Dialog dlg = new ();
+
+        dlg.Ready += Dlg_Ready;
+
+        Run (dlg);
+
+#if DEBUG_IDISPOSABLE
+        Assert.False (dlg.WasDisposed);
+        Assert.False (Top!.WasDisposed);
+        Assert.NotEqual (top, Top);
+        Assert.Equal (dlg, Top);
+#endif
+
+        // dlg wasn't disposed yet and it's possible to access to his properties
+        Assert.False (dlg.Canceled);
+        Assert.NotNull (dlg);
+
+        dlg.Canceled = true;
+        Assert.True (dlg.Canceled);
+
+        dlg.Dispose ();
+        top.Dispose ();
+#if DEBUG_IDISPOSABLE
+        Assert.True (dlg.WasDisposed);
+        Assert.True (Top.WasDisposed);
+        Assert.NotNull (Top);
+#endif
+        Shutdown ();
+        Assert.Null (Top);
+
+        return;
+
+        void Dlg_Ready (object? sender, EventArgs e) { RequestStop (); }
+    }
+
     [Fact]
     [AutoInitShutdown]
     public void Size_Default ()
@@ -1286,7 +1435,7 @@ public class DialogTests (ITestOutputHelper output)
         };
 
         Begin (d);
-        AutoInitShutdownAttribute.FakeResize(new(100, 100));
+        AutoInitShutdownAttribute.FakeResize (new (100, 100));
 
         // Default size is Percent(85) 
         Assert.Equal (new ((int)(100 * .85), (int)(100 * .85)), d.Frame.Size);
@@ -1302,7 +1451,7 @@ public class DialogTests (ITestOutputHelper output)
         var d = new Dialog { Width = 50, Height = 50 };
 
         Begin (d);
-        AutoInitShutdownAttribute.FakeResize(new(100, 100));
+        AutoInitShutdownAttribute.FakeResize (new (100, 100));
 
         // Default size is Percent(85) 
         Assert.Equal (new (50, 50), d.Frame.Size);
@@ -1319,9 +1468,9 @@ public class DialogTests (ITestOutputHelper output)
 
         var buttonRow = $"{Glyphs.VLine}        {Glyphs.VLine}";
         int width = buttonRow.Length;
-        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 3));
 
-        (runState, Dialog dlg) = BeginButtonTestDialog (title, width, Alignment.Center, null);
+        (runState, Dialog dlg) = BeginButtonTestDialog (title, width, Alignment.Center, []);
 
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
 
@@ -1365,159 +1514,4 @@ public class DialogTests (ITestOutputHelper output)
 
         return (runState, dlg);
     }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Run_Does_Not_Dispose_Dialog ()
-    {
-        var top = new Toplevel ();
-
-        Dialog dlg = new ();
-
-        dlg.Ready += Dlg_Ready;
-
-        Run (dlg);
-
-#if DEBUG_IDISPOSABLE
-        Assert.False (dlg.WasDisposed);
-        Assert.False (Top!.WasDisposed);
-        Assert.NotEqual (top, Top);
-        Assert.Equal (dlg, Top);
-#endif
-
-        // dlg wasn't disposed yet and it's possible to access to his properties
-        Assert.False (dlg.Canceled);
-        Assert.NotNull (dlg);
-
-        dlg.Canceled = true;
-        Assert.True (dlg.Canceled);
-
-        dlg.Dispose ();
-        top.Dispose ();
-#if DEBUG_IDISPOSABLE
-        Assert.True (dlg.WasDisposed);
-        Assert.True (Top.WasDisposed);
-        Assert.NotNull (Top);
-#endif
-        Shutdown ();
-        Assert.Null (Top);
-
-        return;
-
-        void Dlg_Ready (object? sender, EventArgs e) { RequestStop (); }
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Can_Access_Cancel_Property_After_Run ()
-    {
-        Dialog dlg = new ();
-
-        dlg.Ready += Dlg_Ready;
-
-        Run (dlg);
-
-#if DEBUG_IDISPOSABLE
-        Assert.False (dlg.WasDisposed);
-        Assert.False (Top!.WasDisposed);
-        Assert.Equal (dlg, Top);
-#endif
-
-        Assert.True (dlg.Canceled);
-
-        // Run it again is possible because it isn't disposed yet
-        Run (dlg);
-
-        // Run another view without dispose the prior will throw an assertion
-#if DEBUG_IDISPOSABLE
-        Dialog dlg2 = new ();
-        dlg2.Ready += Dlg_Ready; 
-        //   Exception exception = Record.Exception (() => Run (dlg2));
-   //     Assert.NotNull (exception);
-
-        dlg.Dispose ();
-
-        // Now it's possible to tun dlg2 without throw
-        Run (dlg2);
-
-        Assert.True (dlg.WasDisposed);
-        Assert.False (Top.WasDisposed);
-        Assert.Equal (dlg2, Top);
-        Assert.False (dlg2.WasDisposed);
-
-        dlg2.Dispose ();
-
-        // tznind REMOVED: Why wouldn't you be able to read cancelled after dispose - that makes no sense
-        // Now an assertion will throw accessing the Canceled property
-        //var exception = Record.Exception (() => Assert.True (dlg.Canceled))!;
-        //Assert.NotNull (exception);
-        //Assert.StartsWith ("Cannot access a disposed object.", exception.Message);
-
-        Assert.True (Top.WasDisposed);
-        Shutdown ();
-        Assert.True (dlg2.WasDisposed);
-        Assert.Null (Top);
-#endif
-
-        return;
-
-        void Dlg_Ready (object? sender, EventArgs e)
-        {
-            ((Dialog)sender!).Canceled = true;
-            RequestStop ();
-        }
-    }
-
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Modal_Captures_All_Mouse ()
-    {
-        Toplevel top = new Toplevel ()
-        {
-            Id = "top",
-        };
-
-        var d = new Dialog
-        {
-            Width = 10,
-            Height = 10,
-            X = 1,
-            Y = 1
-        };
-
-        AutoInitShutdownAttribute.FakeResize(new(20, 20));
-
-        int iterations = 0;
-        Iteration += (s, a) =>
-                     {
-                         if (++iterations > 2)
-                         {
-                             RequestStop ();
-                         }
-
-                         if (iterations == 1)
-                         {
-                             Application.Run (d);
-                             d.Dispose ();
-                         }
-                         else if (iterations == 2)
-                         {
-                             // Mouse click outside of dialog
-                             Application.RaiseMouseEvent (new MouseEventArgs () { Flags = MouseFlags.Button1Clicked, ScreenPosition = new Point (0, 0) });
-                         }
-
-
-                     };
-
-        top.MouseEvent += (s, e) =>
-                          {
-                              // This should not be called because the dialog is modal
-                              Assert.False (true, "Mouse event should not be captured by the top level when a dialog is modal.");
-                          };
-
-        Application.Run (top);
-        top.Dispose ();
-        Application.Shutdown ();
-    }
 }

+ 10 - 28
Tests/UnitTests/FileServices/FileDialogTests.cs

@@ -101,13 +101,13 @@ public class FileDialogTests ()
         Directory.CreateDirectory (openIn);
         dlg.Path = openIn + Path.DirectorySeparatorChar;
 
-        var tf = GetTextField (dlg, FileDialogPart.SearchField);
+        var tf = dlg.SubViews.First (view => view.Id == "_tableViewContainer").SubViews.First (v => v.Id == "_tbFind") as TextField;
         tf.SetFocus ();
 
         Assert.IsType<TextField> (dlg.MostFocused);
         Assert.Same (tf, dlg.MostFocused);
 
-        Assert.Equal ("_Find:", tf.Caption);
+        Assert.Equal ("Find", tf.Caption);
 
         // Dialog has not yet been confirmed with a choice
         Assert.True (dlg.Canceled);
@@ -117,14 +117,14 @@ public class FileDialogTests ()
 
         Assert.True (dlg.Canceled);
 
-        // tabbing out of search 
-        Application.RaiseKeyDownEvent ('\t');
+        //// tabbing out of search 
+        //Application.RaiseKeyDownEvent ('\t');
 
-        //should allow enter to confirm path
-        Application.RaiseKeyDownEvent (Key.Enter);
+        ////should allow enter to confirm path
+        //Application.RaiseKeyDownEvent (Key.Enter);
 
-        // Dialog has not yet been confirmed with a choice
-        Assert.False (dlg.Canceled);
+        //// Dialog has not yet been confirmed with a choice
+        //Assert.False (dlg.Canceled);
         dlg.Dispose ();
     }
 
@@ -456,7 +456,7 @@ public class FileDialogTests ()
          *
          */
 
-        var path = GetTextField (fd, FileDialogPart.Path);
+        var path = fd.SubViews.OfType<TextField> ().ElementAt (0);
         Assert.Equal ("/demo/", path.Text);
 
         var tv = GetTableView (fd);
@@ -529,7 +529,7 @@ public class FileDialogTests ()
          *
          */
 
-        var path = GetTextField (fd, FileDialogPart.Path);
+        var path = fd.SubViews.OfType<TextField> ().ElementAt (0);
         Assert.Equal ("c:\\demo\\",path.Text);
 
         var tv = GetTableView (fd);
@@ -783,19 +783,6 @@ public class FileDialogTests ()
         }
     }
 
-    private TextField GetTextField (FileDialog dlg, FileDialogPart part)
-    {
-        switch (part)
-        {
-            case FileDialogPart.Path:
-                return dlg.SubViews.OfType<TextField> ().ElementAt (0);
-            case FileDialogPart.SearchField:
-                return dlg.SubViews.OfType<TextField> ().ElementAt (1);
-            default:
-                throw new ArgumentOutOfRangeException (nameof (part), part, null);
-        }
-    }
-
     private TableView GetTableView (FileDialog dlg)
     {
         // The table view is in the _tableViewContainer which is a direct subview of the dialog
@@ -822,9 +809,4 @@ public class FileDialogTests ()
         return FindTableView (dlg);
     }
 
-    private enum FileDialogPart
-    {
-        Path,
-        SearchField,
-    }
 }