浏览代码

Merge branch 'v2_develop' into v2_3512-ctrl-z-suspend-unix-fix

BDisp 1 年之前
父节点
当前提交
8b7de1ea7c

+ 35 - 32
Terminal.Gui/Views/ComboBox.cs

@@ -5,7 +5,7 @@
 //   Ross Ferguson ([email protected])
 //
 
-using System.Collections;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 
 namespace Terminal.Gui;
@@ -16,7 +16,7 @@ public class ComboBox : View
     private readonly ComboListView _listview;
     private readonly int _minimumHeight = 2;
     private readonly TextField _search;
-    private readonly IList _searchset = new List<object> ();
+    private readonly ObservableCollection<object> _searchSet = [];
     private bool _autoHide = true;
     private bool _hideDropdownListOnClick;
     private int _lastSelectedItem = -1;
@@ -47,9 +47,9 @@ public class ComboBox : View
 
         _listview.SelectedItemChanged += (sender, e) =>
                                          {
-                                             if (!HideDropdownListOnClick && _searchset.Count > 0)
+                                             if (!HideDropdownListOnClick && _searchSet.Count > 0)
                                              {
-                                                 SetValue (_searchset [_listview.SelectedItem]);
+                                                 SetValue (_searchSet [_listview.SelectedItem]);
                                              }
                                          };
 
@@ -174,7 +174,7 @@ public class ComboBox : View
 
     /// <summary>Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.</summary>
     /// <value>The source.</value>
-    /// <remarks>Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.</remarks>
+    /// <remarks>Use <see cref="SetSource{T}"/> to set a new <see cref="ObservableCollection{T}"/> source.</remarks>
     public IListDataSource Source
     {
         get => _source;
@@ -366,13 +366,13 @@ public class ComboBox : View
     /// <summary>This event is raised when the selected item in the <see cref="ComboBox"/> has changed.</summary>
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
-    /// <summary>Sets the source of the <see cref="ComboBox"/> to an <see cref="IList"/>.</summary>
-    /// <value>An object implementing the IList interface.</value>
+    /// <summary>Sets the source of the <see cref="ComboBox"/> to an <see cref="ObservableCollection{T}"/>.</summary>
+    /// <value>An object implementing the INotifyCollectionChanged and INotifyPropertyChanged interface.</value>
     /// <remarks>
-    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome
+    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public void SetSource (IList source)
+    public void SetSource<T> (ObservableCollection<T> source)
     {
         if (source is null)
         {
@@ -380,7 +380,7 @@ public class ComboBox : View
         }
         else
         {
-            _listview.SetSource (source);
+            _listview.SetSource<T> (source);
             Source = _listview.Source;
         }
     }
@@ -408,7 +408,7 @@ public class ComboBox : View
 
         return Math.Min (
                          Math.Max (Viewport.Height - 1, _minimumHeight - 1),
-                         _searchset?.Count > 0 ? _searchset.Count :
+                         _searchSet?.Count > 0 ? _searchSet.Count :
                          IsShow ? Math.Max (Viewport.Height - 1, _minimumHeight - 1) : 0
                         );
     }
@@ -468,9 +468,9 @@ public class ComboBox : View
             return -1;
         }
 
-        for (var i = 0; i < _searchset.Count; i++)
+        for (var i = 0; i < _searchSet.Count; i++)
         {
-            if (_searchset [i].ToString () == searchText)
+            if (_searchSet [i].ToString () == searchText)
             {
                 return i;
             }
@@ -504,14 +504,14 @@ public class ComboBox : View
         if (_search.HasFocus)
         {
             // jump to list
-            if (_searchset?.Count > 0)
+            if (_searchSet?.Count > 0)
             {
                 _listview.TabStop = true;
                 _listview.SetFocus ();
 
                 if (_listview.SelectedItem > -1)
                 {
-                    SetValue (_searchset [_listview.SelectedItem]);
+                    SetValue (_searchSet [_listview.SelectedItem]);
                 }
                 else
                 {
@@ -572,7 +572,7 @@ public class ComboBox : View
 
     private bool? MoveUpList ()
     {
-        if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchset?.Count > 0) // jump back to search
+        if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchSet?.Count > 0) // jump back to search
         {
             _search.CursorPosition = _search.Text.GetRuneCount ();
             _search.SetFocus ();
@@ -634,7 +634,7 @@ public class ComboBox : View
 
         ResetSearchSet ();
 
-        _listview.SetSource (_searchset);
+        _listview.SetSource (_searchSet);
         _listview.Height = CalculatetHeight ();
 
         if (Subviews.Count > 0 && HasFocus)
@@ -645,7 +645,7 @@ public class ComboBox : View
 
     private void ResetSearchSet (bool noCopy = false)
     {
-        _searchset.Clear ();
+        _searchSet.Clear ();
 
         if (_autoHide || noCopy)
         {
@@ -680,16 +680,19 @@ public class ComboBox : View
             IsShow = true;
             ResetSearchSet (true);
 
-            foreach (object item in _source.ToList ())
+            if (!string.IsNullOrEmpty (_search.Text))
             {
-                // Iterate to preserver object type and force deep copy
-                if (item.ToString ()
-                        .StartsWith (
-                                     _search.Text,
-                                     StringComparison.CurrentCultureIgnoreCase
-                                    ))
+                foreach (object item in _source.ToList ())
                 {
-                    _searchset.Add (item);
+                    // Iterate to preserver object type and force deep copy
+                    if (item.ToString ()
+                            .StartsWith (
+                                         _search.Text,
+                                         StringComparison.CurrentCultureIgnoreCase
+                                        ))
+                    {
+                        _searchSet.Add (item);
+                    }
                 }
             }
         }
@@ -710,7 +713,7 @@ public class ComboBox : View
         IsShow = false;
         _listview.TabStop = false;
 
-        if (_listview.Source.Count == 0 || (_searchset?.Count ?? 0) == 0)
+        if (_listview.Source.Count == 0 || (_searchSet?.Count ?? 0) == 0)
         {
             _text = "";
             HideList ();
@@ -719,7 +722,7 @@ public class ComboBox : View
             return;
         }
 
-        SetValue (_listview.SelectedItem > -1 ? _searchset [_listview.SelectedItem] : _text);
+        SetValue (_listview.SelectedItem > -1 ? _searchSet [_listview.SelectedItem] : _text);
         _search.CursorPosition = _search.Text.GetColumns ();
         Search_Changed (this, new StateEventArgs<string> (_search.Text, _search.Text));
         OnOpenSelectedItem ();
@@ -738,7 +741,7 @@ public class ComboBox : View
         // force deep copy
         foreach (object item in Source.ToList ())
         {
-            _searchset.Add (item);
+            _searchSet.Add (item);
         }
     }
 
@@ -762,7 +765,7 @@ public class ComboBox : View
     /// Consider making public
     private void ShowList ()
     {
-        _listview.SetSource (_searchset);
+        _listview.SetSource (_searchSet);
         _listview.Clear (); 
         _listview.Height = CalculatetHeight ();
         SuperView?.BringSubviewToFront (this);
@@ -784,9 +787,9 @@ public class ComboBox : View
         private bool _isFocusing;
         public ComboListView (ComboBox container, bool hideDropdownListOnClick) { SetInitialProperties (container, hideDropdownListOnClick); }
 
-        public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick)
+        public ComboListView (ComboBox container, ObservableCollection<string> source, bool hideDropdownListOnClick)
         {
-            Source = new ListWrapper (source);
+            Source = new ListWrapper<string> (source);
             SetInitialProperties (container, hideDropdownListOnClick);
         }
 

+ 116 - 31
Terminal.Gui/Views/ListView.cs

@@ -1,11 +1,18 @@
 using System.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
 /// <summary>Implement <see cref="IListDataSource"/> to provide custom rendering for a <see cref="ListView"/>.</summary>
-public interface IListDataSource
+public interface IListDataSource: IDisposable
 {
+    /// <summary>
+    /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
+    /// </summary>
+    event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <summary>Returns the number of elements to display</summary>
     int Count { get; }
 
@@ -64,7 +71,7 @@ public interface IListDataSource
 ///     </para>
 ///     <para>
 ///         By default <see cref="ListView"/> uses <see cref="object.ToString"/> to render the items of any
-///         <see cref="IList"/> object (e.g. arrays, <see cref="List{T}"/>, and other collections). Alternatively, an
+///         <see cref="ObservableCollection{T}"/> object (e.g. arrays, <see cref="List{T}"/>, and other collections). Alternatively, an
 ///         object that implements <see cref="IListDataSource"/> can be provided giving full control of what is rendered.
 ///     </para>
 ///     <para>
@@ -258,12 +265,18 @@ public class ListView : View
         set
         {
             if (_source == value)
-
             {
                 return;
             }
+
+            _source?.Dispose ();
             _source = value;
 
+            if (_source is { })
+            {
+                _source.CollectionChanged += Source_CollectionChanged;
+            }
+
             SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
             if (IsInitialized)
             {
@@ -277,6 +290,20 @@ public class ListView : View
         }
     }
 
+    private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+    {
+        SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
+
+        if (Source is { Count: > 0 } && _selected > Source.Count - 1)
+        {
+            SelectedItem = Source.Count - 1;
+        }
+
+        SetNeedsDisplay ();
+
+        OnCollectionChanged (e);
+    }
+
     /// <summary>Gets or sets the index of the item that will appear at the top of the <see cref="View.Viewport"/>.</summary>
     /// <remarks>
     /// This a helper property for accessing <c>listView.Viewport.Y</c>.
@@ -501,7 +528,12 @@ public class ListView : View
 
             if (Viewport.Y + _selected > Viewport.Height - 1)
             {
-                Viewport = Viewport with { Y = _selected };
+                Viewport = Viewport with
+                {
+                    Y = _selected < Viewport.Height - 1
+                            ? Math.Max (Viewport.Height - _selected + 1, 0)
+                            : Math.Max (_selected - Viewport.Height + 1, 0)
+                };
             }
 
             OnSelectedChanged ();
@@ -796,21 +828,26 @@ public class ListView : View
     /// <summary>This event is raised when the selected item in the <see cref="ListView"/> has changed.</summary>
     public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
+    /// <summary>
+    /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
+    /// </summary>
+    public event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <summary>Sets the source of the <see cref="ListView"/> to an <see cref="IList"/>.</summary>
     /// <value>An object implementing the IList interface.</value>
     /// <remarks>
-    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome
+    ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public void SetSource (IList source)
+    public void SetSource<T> (ObservableCollection<T> source)
     {
-        if (source is null && (Source is null || !(Source is ListWrapper)))
+        if (source is null && Source is not ListWrapper<T>)
         {
             Source = null;
         }
         else
         {
-            Source = new ListWrapper (source);
+            Source = new ListWrapper<T> (source);
         }
     }
 
@@ -820,18 +857,18 @@ public class ListView : View
     ///     Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom
     ///     rendering.
     /// </remarks>
-    public Task SetSourceAsync (IList source)
+    public Task SetSourceAsync<T> (ObservableCollection<T> source)
     {
         return Task.Factory.StartNew (
                                       () =>
                                       {
-                                          if (source is null && (Source is null || !(Source is ListWrapper)))
+                                          if (source is null && (Source is null || !(Source is ListWrapper<T>)))
                                           {
                                               Source = null;
                                           }
                                           else
                                           {
-                                              Source = new ListWrapper (source);
+                                              Source = new ListWrapper<T> (source);
                                           }
 
                                           return source;
@@ -843,35 +880,74 @@ public class ListView : View
     }
 
     private void ListView_LayoutStarted (object sender, LayoutEventArgs e) { EnsureSelectedItemVisible (); }
+    /// <summary>
+    /// Call the event to raises the <see cref="CollectionChanged"/>.
+    /// </summary>
+    /// <param name="e"></param>
+    protected virtual void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        _source?.Dispose ();
+
+        base.Dispose (disposing);
+    }
 }
 
 /// <summary>
 ///     Provides a default implementation of <see cref="IListDataSource"/> that renders <see cref="ListView"/> items
 ///     using <see cref="object.ToString()"/>.
 /// </summary>
-public class ListWrapper : IListDataSource
+public class ListWrapper<T> : IListDataSource, IDisposable
 {
-    private readonly int _count;
-    private readonly BitArray _marks;
-    private readonly IList _source;
+    private int _count;
+    private BitArray _marks;
+    private readonly ObservableCollection<T> _source;
 
     /// <inheritdoc/>
-    public ListWrapper (IList source)
+    public ListWrapper (ObservableCollection<T> source)
     {
         if (source is { })
         {
             _count = source.Count;
             _marks = new BitArray (_count);
             _source = source;
+            _source.CollectionChanged += Source_CollectionChanged;
             Length = GetMaxLengthItem ();
         }
     }
 
+    private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+    {
+        CheckAndResizeMarksIfRequired ();
+        CollectionChanged?.Invoke (sender, e);
+    }
+
+    /// <inheritdoc />
+    public event NotifyCollectionChangedEventHandler CollectionChanged;
+
     /// <inheritdoc/>
-    public int Count => _source is { } ? _source.Count : 0;
+    public int Count => _source?.Count ?? 0;
 
     /// <inheritdoc/>
-    public int Length { get; }
+    public int Length { get; private set; }
+
+    void CheckAndResizeMarksIfRequired ()
+    {
+        if (_source != null && _count != _source.Count)
+        {
+            _count = _source.Count;
+            BitArray newMarks = new BitArray (_count);
+            for (var i = 0; i < Math.Min (_marks.Length, newMarks.Length); i++)
+            {
+                newMarks [i] = _marks [i];
+            }
+            _marks = newMarks;
+
+            Length = GetMaxLengthItem ();
+        }
+    }
 
     /// <inheritdoc/>
     public void Render (
@@ -886,25 +962,25 @@ public class ListWrapper : IListDataSource
     )
     {
         container.Move (Math.Max (col - start, 0), line);
-        object t = _source? [item];
 
-        if (t is null)
-        {
-            RenderUstr (driver, "", col, line, width);
-        }
-        else
+        if (_source is { })
         {
-            if (t is string u)
-            {
-                RenderUstr (driver, u, col, line, width, start);
-            }
-            else if (t is string s)
+            object t = _source [item];
+
+            if (t is null)
             {
-                RenderUstr (driver, s, col, line, width, start);
+                RenderUstr (driver, "", col, line, width);
             }
             else
             {
-                RenderUstr (driver, t.ToString (), col, line, width, start);
+                if (t is string s)
+                {
+                    RenderUstr (driver, s, col, line, width, start);
+                }
+                else
+                {
+                    RenderUstr (driver, t.ToString (), col, line, width, start);
+                }
             }
         }
     }
@@ -1011,4 +1087,13 @@ public class ListWrapper : IListDataSource
             driver.AddRune ((Rune)' ');
         }
     }
+
+    /// <inheritdoc />
+    public void Dispose ()
+    {
+        if (_source is { })
+        {
+            _source.CollectionChanged -= Source_CollectionChanged;
+        }
+    }
 }

+ 4 - 3
UICatalog/KeyBindingsDialog.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -10,7 +11,7 @@ internal class KeyBindingsDialog : Dialog
     // TODO: Update to use Key instead of KeyCode
     private static readonly Dictionary<Command, KeyCode> CurrentBindings = new ();
 
-    private readonly Command [] _commands;
+    private readonly ObservableCollection<Command> _commands;
     private readonly ListView _commandsListView;
     private readonly Label _keyLabel;
 
@@ -26,13 +27,13 @@ internal class KeyBindingsDialog : Dialog
         }
 
         // known commands that views can support
-        _commands = Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ();
+        _commands = new (Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ());
 
         _commandsListView = new ListView
         {
             Width = Dim.Percent (50),
             Height = Dim.Percent (100) - 1,
-            Source = new ListWrapper (_commands),
+            Source = new ListWrapper<Command> (_commands),
             SelectedItem = 0
         };
 

+ 21 - 18
UICatalog/Scenario.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -97,7 +98,7 @@ public class Scenario : IDisposable
     ///     <see cref="ScenarioMetadata.Name"/>.
     ///     https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
     /// </summary>
-    public static List<Scenario> GetScenarios ()
+    public static ObservableCollection<Scenario> GetScenarios ()
     {
         List<Scenario> objects = new ();
 
@@ -113,7 +114,7 @@ public class Scenario : IDisposable
             _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
         }
 
-        return objects.OrderBy (s => s.GetName ()).ToList ();
+        return new (objects.OrderBy (s => s.GetName ()).ToList ());
     }
 
     /// <summary>
@@ -239,24 +240,26 @@ public class Scenario : IDisposable
     #endregion IDispose
 
     /// <summary>Returns a list of all Categories set by all of the <see cref="Scenario"/>s defined in the project.</summary>
-    internal static List<string> GetAllCategories ()
+    internal static ObservableCollection<string> GetAllCategories ()
     {
-        List<string> categories = new ();
-
-        categories = typeof (Scenario).Assembly.GetTypes ()
-                                      .Where (
-                                              myType => myType.IsClass
-                                                        && !myType.IsAbstract
-                                                        && myType.IsSubclassOf (typeof (Scenario)))
-                                      .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
-                                      .Aggregate (
-                                                  categories,
-                                                  (current, attrs) => current
-                                                                      .Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name))
-                                                                      .ToList ());
+        List<string> aCategories = [];
+
+        aCategories = typeof (Scenario).Assembly.GetTypes ()
+                                       .Where (
+                                               myType => myType.IsClass
+                                                         && !myType.IsAbstract
+                                                         && myType.IsSubclassOf (typeof (Scenario)))
+                                       .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
+                                       .Aggregate (
+                                                   aCategories,
+                                                   (current, attrs) => current
+                                                                       .Union (
+                                                                               attrs.Where (a => a is ScenarioCategory)
+                                                                                    .Select (a => ((ScenarioCategory)a).Name))
+                                                                       .ToList ());
 
         // Sort
-        categories = categories.OrderBy (c => c).ToList ();
+        ObservableCollection<string> categories = new (aCategories.OrderBy (c => c).ToList ());
 
         // Put "All" at the top
         categories.Insert (0, "All Scenarios");
@@ -264,7 +267,7 @@ public class Scenario : IDisposable
         return categories;
     }
 
-    /// <summary>Defines the category names used to catagorize a <see cref="Scenario"/></summary>
+    /// <summary>Defines the category names used to categorize a <see cref="Scenario"/></summary>
     [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
     public class ScenarioCategory (string Name) : System.Attribute
     {

+ 4 - 3
UICatalog/Scenarios/AllViewsTester.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Reflection;
 using Terminal.Gui;
@@ -80,7 +81,7 @@ public class AllViewsTester : Scenario
             AllowsMarking = false,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
             SelectedItem = 0,
-            Source = new ListWrapper (_viewClasses.Keys.ToList ())
+            Source = new ListWrapper<string> (new (_viewClasses.Keys.ToList ()))
         };
         _classListView.OpenSelectedItem += (s, a) => { _settingsPane.SetFocus (); };
 
@@ -385,8 +386,8 @@ public class AllViewsTester : Scenario
         // If the view supports a Source property, set it so we have something to look at
         if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
         {
-            var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
-            view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+            var source = new ListWrapper<string> (["Test Text #1", "Test Text #2", "Test Text #3"]);
+            view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, [source]);
         }
 
         // If the view supports a Title property, set it so we have something to look at

+ 5 - 4
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Threading;
@@ -247,7 +248,7 @@ public class BackgroundWorkerCollection : Scenario
         private readonly ListView _listView;
         private readonly Button _start;
 
-        public StagingUIController (Staging staging, List<string> list) : this ()
+        public StagingUIController (Staging staging, ObservableCollection<string> list) : this ()
         {
             Staging = staging;
             _label.Text = "Work list:";
@@ -335,7 +336,7 @@ public class BackgroundWorkerCollection : Scenario
     private class WorkerApp : Toplevel
     {
         private readonly ListView _listLog;
-        private readonly List<string> _log = [];
+        private readonly ObservableCollection<string> _log = [];
         private List<StagingUIController> _stagingsUi;
         private Dictionary<Staging, BackgroundWorker> _stagingWorkers;
 
@@ -357,7 +358,7 @@ public class BackgroundWorkerCollection : Scenario
                 Y = 0,
                 Width = Dim.Fill (),
                 Height = Dim.Fill (),
-                Source = new ListWrapper (_log)
+                Source = new ListWrapper<string> (_log)
             };
             Add (_listLog);
 
@@ -464,7 +465,7 @@ public class BackgroundWorkerCollection : Scenario
                                                           );
                                                  Application.Refresh ();
 
-                                                 var stagingUI = new StagingUIController (staging, e.Result as List<string>)
+                                                 var stagingUI = new StagingUIController (staging, e.Result as ObservableCollection<string>)
                                                  {
                                                      Modal = false,
                                                      Title =

+ 4 - 3
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -16,7 +17,7 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Mouse and Keyboard")]
 public class CollectionNavigatorTester : Scenario
 {
-    private readonly List<string> _items = new []
+    private ObservableCollection<string> _items = new ObservableCollection<string> (new ObservableCollection<string> ()
     {
         "a",
         "b",
@@ -71,7 +72,7 @@ public class CollectionNavigatorTester : Scenario
         "q",
         "quit",
         "quitter"
-    }.ToList ();
+    }.ToList ());
 
     private ListView _listView;
     private TreeView _treeView;
@@ -129,7 +130,7 @@ public class CollectionNavigatorTester : Scenario
 
         Top.Add (menu);
 
-        _items.Sort (StringComparer.OrdinalIgnoreCase);
+        _items = new (_items.OrderBy (i => i, StringComparer.OrdinalIgnoreCase));
 
         CreateListView ();
         var vsep = new LineView (Orientation.Vertical) { X = Pos.Right (_listView), Y = 1, Height = Dim.Fill () };

+ 5 - 4
UICatalog/Scenarios/ComboBoxIteration.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -10,14 +11,14 @@ public class ComboBoxIteration : Scenario
 {
     public override void Setup ()
     {
-        List<string> items = new () { "one", "two", "three" };
+        ObservableCollection<string> items = ["one", "two", "three"];
 
         var lbListView = new Label { Width = 10, Height = 1 };
         Win.Add (lbListView);
 
         var listview = new ListView
         {
-            Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper (items)
+            Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper<string> (items)
         };
         Win.Add (listview);
 
@@ -59,7 +60,7 @@ public class ComboBoxIteration : Scenario
 
         btnTwo.Accept += (s, e) =>
                           {
-                              items = new List<string> { "one", "two" };
+                              items = ["one", "two"];
                               comboBox.SetSource (items);
                               listview.SetSource (items);
                               listview.SelectedItem = 0;
@@ -70,7 +71,7 @@ public class ComboBoxIteration : Scenario
 
         btnThree.Accept += (s, e) =>
                             {
-                                items = new List<string> { "one", "two", "three" };
+                                items =["one", "two", "three"];
                                 comboBox.SetSource (items);
                                 listview.SetSource (items);
                                 listview.SelectedItem = 0;

+ 16 - 17
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -1,6 +1,5 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Reflection;
 using System.Runtime.CompilerServices;
@@ -440,7 +439,7 @@ public class DynamicMenuBar : Scenario
                                     TextTitle.Text = string.Empty;
                                     Application.RequestStop ();
                                 };
-            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel] };
+            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 22, Driver.Rows) };
 
             Width = Dim.Fill ();
             Height = Dim.Fill () - 1;
@@ -596,10 +595,10 @@ public class DynamicMenuBar : Scenario
             var _btnAddMenuBar = new Button { Y = 1, Text = "Add a MenuBar" };
             _frmMenu.Add (_btnAddMenuBar);
 
-            var _btnMenuBarUp = new Button { X = Pos.Center (), Text = "^" };
+            var _btnMenuBarUp = new Button { X = Pos.Center (), Text = CM.Glyphs.UpArrow.ToString () };
             _frmMenu.Add (_btnMenuBarUp);
 
-            var _btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (_btnMenuBarUp), Text = "v" };
+            var _btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (_btnMenuBarUp), Text = CM.Glyphs.DownArrow.ToString () };
             _frmMenu.Add (_btnMenuBarDown);
 
             var _btnRemoveMenuBar = new Button { Y = 1, Text = "Remove a MenuBar" };
@@ -609,7 +608,7 @@ public class DynamicMenuBar : Scenario
 
             var _btnPrevious = new Button
             {
-                X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnAddMenuBar) + 2, Text = "<"
+                X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnAddMenuBar) + 2, Text = CM.Glyphs.LeftArrow.ToString ()
             };
             _frmMenu.Add (_btnPrevious);
 
@@ -617,7 +616,7 @@ public class DynamicMenuBar : Scenario
             _btnAdd.X = Pos.AnchorEnd ();
             _frmMenu.Add (_btnAdd);
 
-            var _btnNext = new Button { X = Pos.X (_btnAdd), Y = Pos.Top (_btnPrevious), Text = ">" };
+            var _btnNext = new Button { X = Pos.X (_btnAdd), Y = Pos.Top (_btnPrevious), Text = CM.Glyphs.RightArrow.ToString () };
             _frmMenu.Add (_btnNext);
 
             var _lblMenuBar = new Label
@@ -657,7 +656,7 @@ public class DynamicMenuBar : Scenario
                 Y = Pos.Top (_btnPrevious) + 2,
                 Width = _lblMenuBar.Width,
                 Height = Dim.Fill (),
-                Source = new ListWrapper (new List<DynamicMenuItemList> ())
+                Source = new ListWrapper<DynamicMenuItemList> ([])
             };
             _frmMenu.Add (_lstMenus);
 
@@ -669,10 +668,10 @@ public class DynamicMenuBar : Scenario
             var _btnRemove = new Button { X = Pos.Left (_btnAdd), Y = Pos.Top (_btnAdd) + 1, Text = "Remove" };
             _frmMenu.Add (_btnRemove);
 
-            var _btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnRemove) + 2, Text = "^" };
+            var _btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () };
             _frmMenu.Add (_btnUp);
 
-            var _btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnUp) + 1, Text = "v" };
+            var _btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () };
             _frmMenu.Add (_btnDown);
 
             Add (_frmMenu);
@@ -857,7 +856,7 @@ public class DynamicMenuBar : Scenario
                                       return;
                                   }
 
-                                  if (!(_currentMenuBarItem is MenuBarItem))
+                                  if (_currentMenuBarItem is not MenuBarItem)
                                   {
                                       var parent = _currentMenuBarItem.Parent as MenuBarItem;
                                       int idx = parent.GetChildrenIndex (_currentMenuBarItem);
@@ -1109,7 +1108,7 @@ public class DynamicMenuBar : Scenario
             SetFrameDetails ();
 
             var ustringConverter = new UStringValueConverter ();
-            var listWrapperConverter = new ListWrapperConverter ();
+            var listWrapperConverter = new ListWrapperConverter<DynamicMenuItemList> ();
 
             var lblMenuBar = new Binding (this, "MenuBar", _lblMenuBar, "Text", ustringConverter);
             var lblParent = new Binding (this, "Parent", _lblParent, "Text", ustringConverter);
@@ -1160,7 +1159,7 @@ public class DynamicMenuBar : Scenario
 
             void SetListViewSource (MenuItem _currentMenuBarItem, bool fill = false)
             {
-                DataContext.Menus = new ();
+                DataContext.Menus = [];
                 var menuBarItem = _currentMenuBarItem as MenuBarItem;
 
                 if (menuBarItem != null && menuBarItem?.Children == null)
@@ -1344,7 +1343,7 @@ public class DynamicMenuBar : Scenario
     public class DynamicMenuItemModel : INotifyPropertyChanged
     {
         private string _menuBar;
-        private List<DynamicMenuItemList> _menus;
+        private ObservableCollection<DynamicMenuItemList> _menus;
         private string _parent;
         public DynamicMenuItemModel () { Menus = []; }
 
@@ -1367,7 +1366,7 @@ public class DynamicMenuBar : Scenario
             }
         }
 
-        public List<DynamicMenuItemList> Menus
+        public ObservableCollection<DynamicMenuItemList> Menus
         {
             get => _menus;
             set
@@ -1414,9 +1413,9 @@ public class DynamicMenuBar : Scenario
         object Convert (object value, object parameter = null);
     }
 
-    public class ListWrapperConverter : IValueConverter
+    public class ListWrapperConverter<T> : IValueConverter
     {
-        public object Convert (object value, object parameter = null) { return new ListWrapper ((IList)value); }
+        public object Convert (object value, object parameter = null) { return new ListWrapper<T> ((ObservableCollection<T>)value); }
     }
 
     public class UStringValueConverter : IValueConverter

+ 14 - 16
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -1,6 +1,5 @@
 using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Reflection;
 using System.Runtime.CompilerServices;
@@ -284,7 +283,7 @@ public class DynamicStatusBar : Scenario
                                       TextTitle.Text = string.Empty;
                                       Application.RequestStop ();
                                   };
-            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel] };
+            var dialog = new Dialog { Title = "Enter the menu details.", Buttons = [btnOk, btnCancel], Height = Dim.Auto (DimAutoStyle.Content, 17, Driver.Rows) };
 
             Width = Dim.Fill ();
             Height = Dim.Fill () - 1;
@@ -375,7 +374,7 @@ public class DynamicStatusBar : Scenario
             _frmStatusBar.Add (_btnRemoveStatusBar);
 
             var _btnAdd = new Button { Y = Pos.Top (_btnRemoveStatusBar) + 2, Text = " Add  " };
-            _btnAdd.X = Pos.AnchorEnd (0);
+            _btnAdd.X = Pos.AnchorEnd ();
             _frmStatusBar.Add (_btnAdd);
 
             _lstItems = new ListView
@@ -384,17 +383,17 @@ public class DynamicStatusBar : Scenario
                 Y = Pos.Top (_btnAddStatusBar) + 2,
                 Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
                 Height = Dim.Fill (),
-                Source = new ListWrapper (new List<DynamicStatusItemList> ())
+                Source = new ListWrapper<DynamicStatusItemList> ([])
             };
             _frmStatusBar.Add (_lstItems);
 
             var _btnRemove = new Button { X = Pos.Left (_btnAdd), Y = Pos.Top (_btnAdd) + 1, Text = "Remove" };
             _frmStatusBar.Add (_btnRemove);
 
-            var _btnUp = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnRemove) + 2, Text = "^" };
+            var _btnUp = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () };
             _frmStatusBar.Add (_btnUp);
 
-            var _btnDown = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnUp) + 1, Text = "v" };
+            var _btnDown = new Button { X = Pos.Right (_lstItems) + 2, Y = Pos.Top (_btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () };
             _frmStatusBar.Add (_btnDown);
 
             Add (_frmStatusBar);
@@ -569,7 +568,7 @@ public class DynamicStatusBar : Scenario
 
                                                Remove (_statusBar);
                                                _statusBar = null;
-                                               DataContext.Items = new List<DynamicStatusItemList> ();
+                                               DataContext.Items = [];
                                                _currentStatusItem = null;
                                                _currentSelectedStatusBar = -1;
                                                SetListViewSource (_currentStatusItem, true);
@@ -579,7 +578,7 @@ public class DynamicStatusBar : Scenario
             SetFrameDetails ();
 
             var ustringConverter = new UStringValueConverter ();
-            var listWrapperConverter = new ListWrapperConverter ();
+            var listWrapperConverter = new ListWrapperConverter<DynamicStatusItemList> ();
 
             var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter);
 
@@ -611,7 +610,7 @@ public class DynamicStatusBar : Scenario
 
             void SetListViewSource (StatusItem _currentStatusItem, bool fill = false)
             {
-                DataContext.Items = new List<DynamicStatusItemList> ();
+                DataContext.Items = [];
                 StatusItem statusItem = _currentStatusItem;
 
                 if (!fill)
@@ -681,10 +680,9 @@ public class DynamicStatusBar : Scenario
             if (split.Length > 1)
             {
                 txt = split [2].Trim ();
-                ;
             }
 
-            if (string.IsNullOrEmpty (shortcut))
+            if (string.IsNullOrEmpty (shortcut) || shortcut == "Null")
             {
                 return txt;
             }
@@ -717,11 +715,11 @@ public class DynamicStatusBar : Scenario
 
     public class DynamicStatusItemModel : INotifyPropertyChanged
     {
-        private List<DynamicStatusItemList> _items;
+        private ObservableCollection<DynamicStatusItemList> _items;
         private string _statusBar;
         public DynamicStatusItemModel () { Items = []; }
 
-        public List<DynamicStatusItemList> Items
+        public ObservableCollection<DynamicStatusItemList> Items
         {
             get => _items;
             set
@@ -768,9 +766,9 @@ public class DynamicStatusBar : Scenario
         object Convert (object value, object parameter = null);
     }
 
-    public class ListWrapperConverter : IValueConverter
+    public class ListWrapperConverter<T> : IValueConverter
     {
-        public object Convert (object value, object parameter = null) { return new ListWrapper ((IList)value); }
+        public object Convert (object value, object parameter = null) { return new ListWrapper<T> ((ObservableCollection<T>)value); }
     }
 
     public class UStringValueConverter : IValueConverter

+ 7 - 7
UICatalog/Scenarios/Keys.cs

@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -9,8 +9,8 @@ public class Keys : Scenario
 {
     public override void Setup ()
     {
-        List<string> keyPressedList = new ();
-        List<string> invokingKeyBindingsList = new ();
+        ObservableCollection<string> keyPressedList = [];
+        ObservableCollection<string> invokingKeyBindingsList = new ();
 
         var editLabel = new Label { X = 0, Y = 0, Text = "Type text here:" };
         Win.Add (editLabel);
@@ -57,7 +57,7 @@ public class Keys : Scenario
         Win.Add (keyLogLabel);
         int maxKeyString = Key.CursorRight.WithAlt.WithCtrl.WithShift.ToString ().Length;
         var yOffset = 1;
-        List<string> keyEventlist = new ();
+        ObservableCollection<string> keyEventlist = new ();
 
         var keyEventListView = new ListView
         {
@@ -65,7 +65,7 @@ public class Keys : Scenario
             Y = Pos.Top (keyLogLabel) + yOffset,
             Width = "Key Down:".Length + maxKeyString,
             Height = Dim.Fill (),
-            Source = new ListWrapper (keyEventlist)
+            Source = new ListWrapper<string> (keyEventlist)
         };
         keyEventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (keyEventListView);
@@ -85,7 +85,7 @@ public class Keys : Scenario
             Y = Pos.Top (onKeyPressedLabel) + yOffset,
             Width = maxKeyString,
             Height = Dim.Fill (),
-            Source = new ListWrapper (keyPressedList)
+            Source = new ListWrapper<string> (keyPressedList)
         };
         onKeyPressedListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (onKeyPressedListView);
@@ -105,7 +105,7 @@ public class Keys : Scenario
             Y = Pos.Top (onInvokingKeyBindingsLabel) + yOffset,
             Width = Dim.Fill (1),
             Height = Dim.Fill (),
-            Source = new ListWrapper (invokingKeyBindingsList)
+            Source = new ListWrapper<string> (invokingKeyBindingsList)
         };
         onInvokingKeyBindingsListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         Win.Add (onInvokingKeyBindingsListView);

+ 76 - 54
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -1,5 +1,7 @@
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using System.Text;
 using JetBrains.Annotations;
 using Terminal.Gui;
@@ -11,25 +13,34 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("ListView")]
 public class ListViewWithSelection : Scenario
 {
-    public CheckBox _allowMarkingCB;
-    public CheckBox _allowMultipleCB;
-    public CheckBox _customRenderCB;
-    public ListView _listView;
-    public List<Scenario> _scenarios;
-
-    public override void Setup ()
+    private CheckBox _allowMarkingCB;
+    private CheckBox _allowMultipleCB;
+    private CheckBox _customRenderCB;
+    private ListView _listView;
+    private ObservableCollection<Scenario> _scenarios;
+    private Window _appWindow;
+
+    /// <inheritdoc />
+    public override void Main ()
     {
+        Application.Init ();
+
+        _appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+        };
+
         _scenarios = GetScenarios ();
 
         _customRenderCB = new CheckBox { X = 0, Y = 0, Text = "Use custom rendering" };
-        Win.Add (_customRenderCB);
+        _appWindow.Add (_customRenderCB);
         _customRenderCB.Toggled += _customRenderCB_Toggled;
 
         _allowMarkingCB = new CheckBox
         {
             X = Pos.Right (_customRenderCB) + 1, Y = 0, Text = "Allow Marking", AllowNullChecked = false
         };
-        Win.Add (_allowMarkingCB);
+        _appWindow.Add (_allowMarkingCB);
         _allowMarkingCB.Toggled += AllowMarkingCB_Toggled;
 
         _allowMultipleCB = new CheckBox
@@ -39,7 +50,7 @@ public class ListViewWithSelection : Scenario
             Visible = (bool)_allowMarkingCB.Checked,
             Text = "Allow Multi-Select"
         };
-        Win.Add (_allowMultipleCB);
+        _appWindow.Add (_allowMultipleCB);
         _allowMultipleCB.Toggled += AllowMultipleCB_Toggled;
 
         _listView = new ListView
@@ -54,42 +65,42 @@ public class ListViewWithSelection : Scenario
             AllowsMultipleSelection = false
         };
         _listView.RowRender += ListView_RowRender;
-        Win.Add (_listView);
+        _appWindow.Add (_listView);
 
         var scrollBar = new ScrollBarView (_listView, true);
 
         scrollBar.ChangedPosition += (s, e) =>
-                                     {
-                                         _listView.TopItem = scrollBar.Position;
+        {
+            _listView.TopItem = scrollBar.Position;
 
-                                         if (_listView.TopItem != scrollBar.Position)
-                                         {
-                                             scrollBar.Position = _listView.TopItem;
-                                         }
+            if (_listView.TopItem != scrollBar.Position)
+            {
+                scrollBar.Position = _listView.TopItem;
+            }
 
-                                         _listView.SetNeedsDisplay ();
-                                     };
+            _listView.SetNeedsDisplay ();
+        };
 
         scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
-                                                        {
-                                                            _listView.LeftItem = scrollBar.OtherScrollBarView.Position;
+        {
+            _listView.LeftItem = scrollBar.OtherScrollBarView.Position;
 
-                                                            if (_listView.LeftItem != scrollBar.OtherScrollBarView.Position)
-                                                            {
-                                                                scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
-                                                            }
+            if (_listView.LeftItem != scrollBar.OtherScrollBarView.Position)
+            {
+                scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+            }
 
-                                                            _listView.SetNeedsDisplay ();
-                                                        };
+            _listView.SetNeedsDisplay ();
+        };
 
         _listView.DrawContent += (s, e) =>
-                                 {
-                                     scrollBar.Size = _listView.Source.Count;
-                                     scrollBar.Position = _listView.TopItem;
-                                     scrollBar.OtherScrollBarView.Size = _listView.MaxLength;
-                                     scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
-                                     scrollBar.Refresh ();
-                                 };
+        {
+            scrollBar.Size = _listView.Source.Count;
+            scrollBar.Position = _listView.TopItem;
+            scrollBar.OtherScrollBarView.Size = _listView.MaxLength;
+            scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+            scrollBar.Refresh ();
+        };
 
         _listView.SetSource (_scenarios);
 
@@ -100,7 +111,11 @@ public class ListViewWithSelection : Scenario
             X = Pos.AnchorEnd (k.Length + 3), Y = 0, Text = k, Checked = scrollBar.AutoHideScrollBars
         };
         keepCheckBox.Toggled += (s, e) => scrollBar.KeepContentAlwaysInViewport = (bool)keepCheckBox.Checked;
-        Win.Add (keepCheckBox);
+        _appWindow.Add (keepCheckBox);
+
+        Application.Run (_appWindow);
+        _appWindow.Dispose ();
+        Application.Shutdown ();
     }
 
     private void _customRenderCB_Toggled (object sender, StateEventArgs<bool?> stateEventArgs)
@@ -114,20 +129,20 @@ public class ListViewWithSelection : Scenario
             _listView.Source = new ScenarioListDataSource (_scenarios);
         }
 
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void AllowMarkingCB_Toggled (object sender, [NotNull] StateEventArgs<bool?> stateEventArgs)
     {
         _listView.AllowsMarking = (bool)!stateEventArgs.OldValue;
         _allowMultipleCB.Visible = _listView.AllowsMarking;
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void AllowMultipleCB_Toggled (object sender, [NotNull] StateEventArgs<bool?> stateEventArgs)
     {
         _listView.AllowsMultipleSelection = (bool)!stateEventArgs.OldValue;
-        Win.SetNeedsDisplay ();
+        _appWindow.SetNeedsDisplay ();
     }
 
     private void ListView_RowRender (object sender, ListViewRowEventArgs obj)
@@ -158,21 +173,21 @@ public class ListViewWithSelection : Scenario
     internal class ScenarioListDataSource : IListDataSource
     {
         private readonly int _nameColumnWidth = 30;
-        private int count;
-        private BitArray marks;
-        private List<Scenario> scenarios;
-        public ScenarioListDataSource (List<Scenario> itemList) { Scenarios = itemList; }
+        private int _count;
+        private BitArray _marks;
+        private ObservableCollection<Scenario> _scenarios;
+        public ScenarioListDataSource (ObservableCollection<Scenario> itemList) { Scenarios = itemList; }
 
-        public List<Scenario> Scenarios
+        public ObservableCollection<Scenario> Scenarios
         {
-            get => scenarios;
+            get => _scenarios;
             set
             {
                 if (value != null)
                 {
-                    count = value.Count;
-                    marks = new BitArray (count);
-                    scenarios = value;
+                    _count = value.Count;
+                    _marks = new BitArray (_count);
+                    _scenarios = value;
                     Length = GetMaxLengthItem ();
                 }
             }
@@ -180,14 +195,16 @@ public class ListViewWithSelection : Scenario
 
         public bool IsMarked (int item)
         {
-            if (item >= 0 && item < count)
+            if (item >= 0 && item < _count)
             {
-                return marks [item];
+                return _marks [item];
             }
 
             return false;
         }
 
+        /// <inheritdoc />
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
         public int Count => Scenarios != null ? Scenarios.Count : 0;
         public int Length { get; private set; }
 
@@ -214,9 +231,9 @@ public class ListViewWithSelection : Scenario
 
         public void SetMark (int item, bool value)
         {
-            if (item >= 0 && item < count)
+            if (item >= 0 && item < _count)
             {
-                marks [item] = value;
+                _marks [item] = value;
             }
         }
 
@@ -224,17 +241,17 @@ public class ListViewWithSelection : Scenario
 
         private int GetMaxLengthItem ()
         {
-            if (scenarios?.Count == 0)
+            if (_scenarios?.Count == 0)
             {
                 return 0;
             }
 
             var maxLength = 0;
 
-            for (var i = 0; i < scenarios.Count; i++)
+            for (var i = 0; i < _scenarios.Count; i++)
             {
                 string s = string.Format (
-                                          string.Format ("{{0,{0}}}", -_nameColumnWidth),
+                                          $"{{0,{-_nameColumnWidth}}}",
                                           Scenarios [i].GetName ()
                                          );
                 var sc = $"{s}  {Scenarios [i].GetDescription ()}";
@@ -276,5 +293,10 @@ public class ListViewWithSelection : Scenario
                 used++;
             }
         }
+
+        public void Dispose ()
+        {
+            _scenarios = null;
+        }
     }
 }

+ 10 - 10
UICatalog/Scenarios/ListsAndCombos.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
 using Terminal.Gui;
@@ -15,19 +15,19 @@ public class ListsAndCombos : Scenario
     public override void Setup ()
     {
         //TODO: Duplicated code in Demo.cs Consider moving to shared assembly
-        List<string> items = new ();
+        ObservableCollection<string> items = [];
 
         foreach (string dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" })
         {
             if (Directory.Exists (dir))
             {
-                items = Directory.GetFiles (dir)
-                                 .Union (Directory.GetDirectories (dir))
-                                 .Select (Path.GetFileName)
-                                 .Where (x => char.IsLetterOrDigit (x [0]))
-                                 .OrderBy (x => x)
-                                 .Select (x => x)
-                                 .ToList ();
+                items = new (Directory.GetFiles (dir)
+                                      .Union (Directory.GetDirectories (dir))
+                                      .Select (Path.GetFileName)
+                                      .Where (x => char.IsLetterOrDigit (x [0]))
+                                      .OrderBy (x => x)
+                                      .Select (x => x)
+                                      .ToList ());
             }
         }
 
@@ -47,7 +47,7 @@ public class ListsAndCombos : Scenario
             Y = Pos.Bottom (lbListView) + 1,
             Height = Dim.Fill (2),
             Width = Dim.Percent (40),
-            Source = new ListWrapper (items)
+            Source = new ListWrapper<string> (items)
         };
         listview.SelectedItemChanged += (s, e) => lbListView.Text = items [listview.SelectedItem];
         Win.Add (lbListView, listview);

+ 2 - 2
UICatalog/Scenarios/Localization.cs

@@ -112,10 +112,10 @@ public class Localization : Scenario
             Width = _cultureInfoNameSource.Select (cn => cn.Length + 3).Max (),
             Height = _cultureInfoNameSource.Length + 1,
             HideDropdownListOnClick = true,
-            Source = new ListWrapper (_cultureInfoNameSource),
+            Source = new ListWrapper<string> (new (_cultureInfoNameSource)),
             SelectedItem = _cultureInfoNameSource.Length - 1
         };
-        _languageComboBox.SetSource (_cultureInfoNameSource);
+        _languageComboBox.SetSource<string> (new (_cultureInfoNameSource));
         _languageComboBox.SelectedItemChanged += LanguageComboBox_SelectChanged;
         Win.Add (_languageComboBox);
 

+ 5 - 5
UICatalog/Scenarios/Mouse.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -113,7 +113,7 @@ public class Mouse : Scenario
             Y = Pos.Bottom (demo)
         };
 
-        List<string> appLogList = new ();
+        ObservableCollection<string> appLogList = new ();
 
         var appLog = new ListView
         {
@@ -122,7 +122,7 @@ public class Mouse : Scenario
             Width = 50,
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (appLogList)
+            Source = new ListWrapper<string> (appLogList)
         };
         win.Add (label, appLog);
 
@@ -144,7 +144,7 @@ public class Mouse : Scenario
             X = Pos.Right (appLog) + 1,
             Y = Pos.Top (label)
         };
-        List<string> winLogList = new ();
+        ObservableCollection<string> winLogList = new ();
 
         var winLog = new ListView
         {
@@ -153,7 +153,7 @@ public class Mouse : Scenario
             Width = Dim.Percent (50),
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (winLogList)
+            Source = new ListWrapper<string> (winLogList)
         };
         win.Add (label, winLog);
 

+ 5 - 3
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Threading;
 using Terminal.Gui;
@@ -261,9 +262,10 @@ public class ProgressBarStyles : Scenario
         container.Add (marqueesContinuousPB);
 
         _pbList.SetSource (
-                          container.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
-                                   .Select (v => v.Title)
-                                   .ToList ()
+                          new ObservableCollection<string> (
+                                                            container.Subviews.Where (v => v.GetType () == typeof (ProgressBar))
+                                                                     .Select (v => v.Title)
+                                                                     .ToList ())
                          );
 
         _pbList.SelectedItemChanged += (sender, e) =>

+ 6 - 6
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
-using System.Diagnostics;
 using System.Threading;
 using Terminal.Gui;
 
@@ -24,7 +24,7 @@ public class SingleBackgroundWorker : Scenario
     public class MainApp : Toplevel
     {
         private readonly ListView _listLog;
-        private readonly List<string> _log = new ();
+        private readonly ObservableCollection<string> _log = [];
         private DateTime? _startStaging;
         private BackgroundWorker _worker;
 
@@ -90,7 +90,7 @@ public class SingleBackgroundWorker : Scenario
                 Y = 2,
                 Width = Dim.Fill (),
                 Height = Dim.Fill (),
-                Source = new ListWrapper (_log)
+                Source = new ListWrapper<string> (_log)
             };
             workerLogTop.Add (_listLog);
             Add (workerLogTop);
@@ -194,7 +194,7 @@ public class SingleBackgroundWorker : Scenario
                                                   Application.Refresh ();
 
                                                   var builderUI =
-                                                      new StagingUIController (_startStaging, e.Result as List<string>);
+                                                      new StagingUIController (_startStaging, e.Result as ObservableCollection<string>);
                                                   var top = Application.Top;
                                                   top.Visible = false;
                                                   Application.Current.Visible = false;
@@ -215,7 +215,7 @@ public class SingleBackgroundWorker : Scenario
     {
         private Toplevel _top;
 
-        public StagingUIController (DateTime? start, List<string> list)
+        public StagingUIController (DateTime? start, ObservableCollection<string> list)
         {
             _top = new Toplevel
             {
@@ -302,7 +302,7 @@ public class SingleBackgroundWorker : Scenario
                      Y = 0,
                      Width = Dim.Fill (),
                      Height = Dim.Fill (),
-                     Source = new ListWrapper (list)
+                     Source = new ListWrapper<string> (list)
                  }
                 );
 

+ 2 - 1
UICatalog/Scenarios/SpinnerStyles.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using Terminal.Gui;
 
@@ -116,7 +117,7 @@ public class SpinnerViewStyles : Scenario
         {
             X = Pos.Center (), Y = Pos.Bottom (preview) + 2, Height = Dim.Fill (), Width = Dim.Fill (1)
         };
-        styles.SetSource (styleArray);
+        styles.SetSource (new ObservableCollection<string> (styleArray));
         styles.SelectedItem = 0; // SpinnerStyle.Custom;
         app.Add (styles);
         SetCustom ();

+ 11 - 11
UICatalog/Scenarios/Threading.cs

@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Threading;
 using System.Threading.Tasks;
 using Terminal.Gui;
@@ -10,7 +10,7 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("Threading")]
 public class Threading : Scenario
 {
-    private readonly List<string> _log = [];
+    private readonly ObservableCollection<string> _log = [];
     private Action _action;
     private Button _btnActionCancel;
     private CancellationTokenSource _cancellationTokenSource;
@@ -28,7 +28,7 @@ public class Threading : Scenario
                   {
                       _itemsList.Source = null;
                       LogJob ("Loading task lambda");
-                      List<string> items = await LoadDataAsync ();
+                      ObservableCollection<string> items = await LoadDataAsync ();
                       LogJob ("Returning from task lambda");
                       await _itemsList.SetSourceAsync (items);
                   };
@@ -37,7 +37,7 @@ public class Threading : Scenario
                    {
                        _itemsList.Source = null;
                        LogJob ("Loading task handler");
-                       List<string> items = await LoadDataAsync ();
+                       ObservableCollection<string> items = await LoadDataAsync ();
                        LogJob ("Returning from task handler");
                        await _itemsList.SetSourceAsync (items);
                    };
@@ -47,7 +47,7 @@ public class Threading : Scenario
                     _itemsList.Source = null;
                     LogJob ("Loading task synchronous");
 
-                    List<string> items =
+                    ObservableCollection<string> items =
                         ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
                     LogJob ("Returning from task synchronous");
                     _itemsList.SetSource (items);
@@ -76,7 +76,7 @@ public class Threading : Scenario
             Width = 50,
             Height = Dim.Fill (),
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (_log)
+            Source = new ListWrapper<string> (_log)
         };
 
         var text = new TextField { X = 1, Y = 3, Width = 100, Text = "Type anything after press the button" };
@@ -148,7 +148,7 @@ public class Threading : Scenario
             }
 
             LogJob ($"Calling task Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
-            List<string> items = await Task.Run (LoadItemsAsync, _cancellationTokenSource.Token);
+            ObservableCollection<string> items = await Task.Run (LoadItemsAsync, _cancellationTokenSource.Token);
 
             if (!_cancellationTokenSource.IsCancellationRequested)
             {
@@ -177,12 +177,12 @@ public class Threading : Scenario
     {
         _itemsList.Source = null;
         LogJob ("Loading task");
-        List<string> items = await LoadDataAsync ();
+        ObservableCollection<string> items = await LoadDataAsync ();
         LogJob ("Returning from task");
         await _itemsList.SetSourceAsync (items);
     }
 
-    private async Task<List<string>> LoadDataAsync ()
+    private async Task<ObservableCollection<string>> LoadDataAsync ()
     {
         _itemsList.Source = null;
         LogJob ("Starting delay");
@@ -211,7 +211,7 @@ public class Threading : Scenario
         ];
     }
 
-    private async Task<List<string>> LoadItemsAsync ()
+    private async Task<ObservableCollection<string>> LoadItemsAsync ()
     {
         // Do something that takes lot of times.
         LogJob ($"Starting delay Thread:{Thread.CurrentThread.ManagedThreadId} {DateTime.Now}");
@@ -231,7 +231,7 @@ public class Threading : Scenario
     {
         _itemsList.Source = null;
         LogJob ("Loading task method");
-        List<string> items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
+        ObservableCollection<string> items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
         await Task.Delay (3000);
         LogJob ("Returning from task method");
         await _itemsList.SetSourceAsync (items);

+ 5 - 5
UICatalog/Scenarios/Unicode.cs

@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Text;
 using Terminal.Gui;
@@ -140,7 +140,7 @@ public class UnicodeInMenu : Scenario
         label = new() { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" };
         Win.Add (label);
         var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
-        comboBox.SetSource (new List<string> { gitString, "Со_хранить" });
+        comboBox.SetSource (new ObservableCollection<string> { gitString, "Со_хранить" });
 
         Win.Add (comboBox);
         comboBox.Text = gitString;
@@ -163,9 +163,9 @@ public class UnicodeInMenu : Scenario
             Y = Pos.Y (label),
             Width = Dim.Percent (60),
             Height = 3,
-            Source = new ListWrapper (
-                                      new List<string> { "item #1", gitString, "Со_хранить", unicode }
-                                     )
+            Source = new ListWrapper<string> (
+                                              ["item #1", gitString, "Со_хранить", unicode]
+                                             )
         };
         Win.Add (listView);
 

+ 12 - 11
UICatalog/UICatalog.cs

@@ -2,6 +2,7 @@ global using Attribute = Terminal.Gui.Attribute;
 global using CM = Terminal.Gui.ConfigurationManager;
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.CommandLine;
 using System.Diagnostics;
 using System.Globalization;
@@ -54,14 +55,14 @@ internal class UICatalogApp
     // main app UI can be restored to previous state
     private static int _cachedScenarioIndex;
     private static string? _cachedTheme = string.Empty;
-    private static List<string>? _categories;
+    private static ObservableCollection<string>? _categories;
     private static readonly FileSystemWatcher _currentDirWatcher = new ();
     private static ViewDiagnosticFlags _diagnosticFlags;
     private static string _forceDriver = string.Empty;
     private static readonly FileSystemWatcher _homeDirWatcher = new ();
     private static bool _isFirstRunning = true;
     private static Options _options;
-    private static List<Scenario>? _scenarios;
+    private static ObservableCollection<Scenario>? _scenarios;
 
     // If set, holds the scenario the user selected
     private static Scenario? _selectedScenario;
@@ -280,11 +281,12 @@ internal class UICatalogApp
         {
             _topLevelColorScheme = "Base";
 
-            int item = _scenarios!.FindIndex (
-                                              s =>
-                                                  s.GetName ()
-                                                   .Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
-                                             );
+            int item = _scenarios!.IndexOf (
+                                            _scenarios!.FirstOrDefault (
+                                                                        s =>
+                                                                            s.GetName ()
+                                                                             .Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
+                                                                       )!);
             _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
 
             Application.Init (driverName: _forceDriver);
@@ -492,7 +494,7 @@ internal class UICatalogApp
                 Title = "_Categories",
                 BorderStyle = LineStyle.Single,
                 SuperViewRendersLineCanvas = true,
-                Source = new ListWrapper (_categories)
+                Source = new ListWrapper<string> (_categories)
             };
             CategoryList.OpenSelectedItem += (s, a) => { ScenarioList!.SetFocus (); };
             CategoryList.SelectedItemChanged += CategoryView_SelectedChanged;
@@ -691,17 +693,16 @@ internal class UICatalogApp
         private void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
         {
             string item = _categories! [e!.Item];
-            List<Scenario> newlist;
+            ObservableCollection<Scenario> newlist;
 
             if (e.Item == 0)
             {
                 // First category is "All"
                 newlist = _scenarios!;
-                newlist = _scenarios!;
             }
             else
             {
-                newlist = _scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ();
+                newlist = new (_scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ());
             }
 
             ScenarioList.Table = new EnumerableTableSource<Scenario> (

+ 5 - 4
UnitTests/UICatalog/ScenarioTests.cs

@@ -1,3 +1,4 @@
+using System.Collections.ObjectModel;
 using System.Reflection;
 using Xunit.Abstractions;
 
@@ -149,7 +150,7 @@ public class ScenarioTests : TestsAllViews
             Height = Dim.Fill (),
             AllowsMarking = false,
             ColorScheme = Colors.ColorSchemes ["TopLevel"],
-            Source = new ListWrapper (_viewClasses.Keys.ToList ())
+            Source = new ListWrapper<string> (new (_viewClasses.Keys.ToList ()))
         };
         _leftPane.Add (_classListView);
 
@@ -511,7 +512,7 @@ public class ScenarioTests : TestsAllViews
                 && view.GetType ().GetProperty ("Source") != null
                 && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
             {
-                var source = new ListWrapper (new List<string> { "Test Text #1", "Test Text #2", "Test Text #3" });
+                var source = new ListWrapper<string> (["Test Text #1", "Test Text #2", "Test Text #3"]);
                 view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
             }
 
@@ -536,10 +537,10 @@ public class ScenarioTests : TestsAllViews
     [Fact]
     public void Run_Generic ()
     {
-        List<Scenario> scenarios = Scenario.GetScenarios ();
+        ObservableCollection<Scenario> scenarios = Scenario.GetScenarios ();
         Assert.NotEmpty (scenarios);
 
-        int item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
+        int item = scenarios.IndexOf (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
         Scenario generic = scenarios [item];
 
         Application.Init (new FakeDriver ());

+ 20 - 19
UnitTests/Views/ComboBoxTests.cs

@@ -1,4 +1,5 @@
-using Xunit.Abstractions;
+using System.Collections.ObjectModel;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
 
@@ -9,7 +10,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var cb = new ComboBox
         {
-            Source = new ListWrapper (new List<string> { "One", "Two", "Three" }), SelectedItem = 1
+            Source = new ListWrapper<string> (["One", "Two", "Three"]), SelectedItem = 1
         };
         cb.BeginInit ();
         cb.EndInit ();
@@ -48,7 +49,7 @@ public class ComboBoxTests (ITestOutputHelper output)
             Y = 2,
             Width = 10,
             Height = 20,
-            Source = new ListWrapper (new List<string> { "One", "Two", "Three" })
+            Source = new ListWrapper<string> (["One", "Two", "Three"])
         };
         cb.BeginInit ();
         cb.EndInit ();
@@ -58,7 +59,7 @@ public class ComboBoxTests (ITestOutputHelper output)
         Assert.Equal (new Rectangle (1, 2, 10, 20), cb.Frame);
         Assert.Equal (-1, cb.SelectedItem);
 
-        cb = new ComboBox { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        cb = new ComboBox { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         cb.BeginInit ();
         cb.EndInit ();
         cb.LayoutSubviews ();
@@ -74,7 +75,7 @@ public class ComboBoxTests (ITestOutputHelper output)
         var comboBox = new ComboBox { Text = "0" };
 
         string [] source = Enumerable.Range (0, 15).Select (x => x.ToString ()).ToArray ();
-        comboBox.SetSource (source);
+        comboBox.SetSource (new ObservableCollection<string> (source.ToList ()));
 
         var top = new Toplevel ();
         top.Add (comboBox);
@@ -90,7 +91,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     public void Expanded_Collapsed_Events ()
     {
         var cb = new ComboBox { Height = 4, Width = 5 };
-        List<string> list = new () { "One", "Two", "Three" };
+        ObservableCollection<string> list = ["One", "Two", "Three"];
 
         cb.Expanded += (s, e) => cb.SetSource (list);
         cb.Collapsed += (s, e) => cb.Source = null;
@@ -124,7 +125,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -179,7 +180,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -216,7 +217,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = false, ReadOnly = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -275,7 +276,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5 };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -377,7 +378,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -496,7 +497,7 @@ public class ComboBoxTests (ITestOutputHelper output)
     {
         var selected = "";
         var cb = new ComboBox { Width = 6, Height = 4, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -654,7 +655,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -717,7 +718,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -775,7 +776,7 @@ Three ",
     {
         var selected = "";
         var cb = new ComboBox { Height = 4, Width = 5, HideDropdownListOnClick = true };
-        cb.SetSource (new List<string> { "One", "Two", "Three" });
+        cb.SetSource (["One", "Two", "Three"]);
         cb.OpenSelectedItem += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (cb);
@@ -809,7 +810,7 @@ Three ",
     [AutoInitShutdown]
     public void KeyBindings_Command ()
     {
-        List<string> source = new () { "One", "Two", "Three" };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
         var cb = new ComboBox { Width = 10 };
         cb.SetSource (source);
         var top = new Toplevel ();
@@ -826,7 +827,7 @@ Three ",
         Assert.True (opened);
         Assert.Equal ("Tw", cb.Text);
         Assert.False (cb.IsShow);
-        cb.SetSource (null);
+        cb.SetSource<string> (null);
         Assert.False (cb.NewKeyDownEvent (Key.Enter));
         Assert.True (cb.NewKeyDownEvent (Key.F4)); // with no source also expand empty
         Assert.True (cb.IsShow);
@@ -975,7 +976,7 @@ Three
         top.FocusFirst ();
         Assert.Null (cb.Source);
         Assert.Equal (-1, cb.SelectedItem);
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
         cb.SetSource (source);
         Assert.NotNull (cb.Source);
         Assert.Equal (0, cb.Source.Count);
@@ -1003,7 +1004,7 @@ Three
         Assert.False (cb.IsShow);
         Assert.Equal (-1, cb.SelectedItem); // retains last accept selected item
         Assert.Equal ("", cb.Text); // clear text
-        cb.SetSource (new List<string> ());
+        cb.SetSource (new ObservableCollection<string> ());
         Assert.Equal (0, cb.Source.Count);
         Assert.Equal (-1, cb.SelectedItem);
         Assert.Equal ("", cb.Text);

+ 308 - 40
UnitTests/Views/ListViewTests.cs

@@ -1,4 +1,6 @@
 using System.Collections;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using System.ComponentModel;
 using Xunit.Abstractions;
 
@@ -14,7 +16,7 @@ public class ListViewTests (ITestOutputHelper output)
         Assert.True (lv.CanFocus);
         Assert.Equal (-1, lv.SelectedItem);
 
-        lv = new() { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        lv = new () { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.NotNull (lv.Source);
         Assert.Equal (-1, lv.SelectedItem);
 
@@ -24,7 +26,7 @@ public class ListViewTests (ITestOutputHelper output)
 
         lv = new()
         {
-            Y = 1, Width = 10, Height = 20, Source = new ListWrapper (new List<string> { "One", "Two", "Three" })
+            Y = 1, Width = 10, Height = 20, Source = new ListWrapper<string> (["One", "Two", "Three"])
         };
         Assert.NotNull (lv.Source);
         Assert.Equal (-1, lv.SelectedItem);
@@ -40,14 +42,14 @@ public class ListViewTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp ()
     {
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
 
         for (var i = 0; i < 20; i++)
         {
             source.Add ($"Line{i}");
         }
 
-        var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill (), Source = new ListWrapper (source) };
+        var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill (), Source = new ListWrapper<string> (source) };
         var win = new Window ();
         win.Add (lv);
         var top = new Toplevel ();
@@ -124,16 +126,16 @@ public class ListViewTests (ITestOutputHelper output)
         TestHelpers.AssertDriverContentsWithFrameAre (
                                                       @"
 ┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
 │Line19    │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
 └──────────┘",
                                                       output
                                                      );
@@ -291,14 +293,14 @@ public class ListViewTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void EnsureSelectedItemVisible_SelectedItem ()
     {
-        List<string> source = new ();
+        ObservableCollection<string> source = [];
 
         for (var i = 0; i < 10; i++)
         {
             source.Add ($"Item {i}");
         }
 
-        var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper (source) };
+        var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper<string> (source) };
         var top = new Toplevel ();
         top.Add (lv);
         Application.Begin (top);
@@ -333,8 +335,8 @@ Item 6",
     [AutoInitShutdown]
     public void EnsureSelectedItemVisible_Top ()
     {
-        List<string> source = new () { "First", "Second" };
-        var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["First", "Second"];
+        var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper<string> (source) };
         lv.SelectedItem = 1;
         var top = new Toplevel ();
         top.Add (lv);
@@ -366,8 +368,8 @@ Item 6",
     [Fact]
     public void KeyBindings_Command ()
     {
-        List<string> source = new () { "One", "Two", "Three" };
-        var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper<string> (source) };
         lv.BeginInit ();
         lv.EndInit ();
         Assert.Equal (-1, lv.SelectedItem);
@@ -424,8 +426,8 @@ Item 6",
     [Fact]
     public void Accept_Command_Accepts_and_Opens_Selected_Item ()
     {
-        List<string> source = ["One", "Two", "Three"];
-        var listView = new ListView { Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var listView = new ListView { Source = new ListWrapper<string> (source) };
         listView.SelectedItem = 0;
 
         var accepted = false;
@@ -455,8 +457,8 @@ Item 6",
     [Fact]
     public void Accept_Cancel_Event_Prevents_OpenSelectedItem ()
     {
-        List<string> source = ["One", "Two", "Three"];
-        var listView = new ListView { Source = new ListWrapper (source) };
+        ObservableCollection<string> source = ["One", "Two", "Three"];
+        var listView = new ListView { Source = new ListWrapper<string> (source) };
         listView.SelectedItem = 0;
 
         var accepted = false;
@@ -494,7 +496,7 @@ Item 6",
     [Fact]
     public void ListViewProcessKeyReturnValue_WithMultipleCommands ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three", "Four" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three", "Four"]) };
 
         Assert.NotNull (lv.Source);
 
@@ -512,7 +514,7 @@ Item 6",
         Assert.Equal (1, lv.SelectedItem);
 
         // clear the items
-        lv.SetSource (null);
+        lv.SetSource<string> (null);
 
         // Press key combo again - return should be false this time as none of the Commands are allowable
         Assert.False (lv.NewKeyDownEvent (ev), "We cannot move down so will not respond to this");
@@ -521,7 +523,7 @@ Item 6",
     [Fact]
     public void ListViewSelectThenDown ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         lv.AllowsMarking = true;
 
         Assert.NotNull (lv.Source);
@@ -585,7 +587,7 @@ Item 6",
     [Fact]
     public void ListWrapper_StartsWith ()
     {
-        var lw = new ListWrapper (new List<string> { "One", "Two", "Three" });
+        var lw = new ListWrapper<string> (["One", "Two", "Three"]);
 
         Assert.Equal (1, lw.StartsWith ("t"));
         Assert.Equal (1, lw.StartsWith ("tw"));
@@ -594,7 +596,7 @@ Item 6",
         Assert.Equal (1, lw.StartsWith ("TW"));
         Assert.Equal (2, lw.StartsWith ("TH"));
 
-        lw = new (new List<string> { "One", "Two", "Three" });
+        lw = new (["One", "Two", "Three"]);
 
         Assert.Equal (1, lw.StartsWith ("t"));
         Assert.Equal (1, lw.StartsWith ("tw"));
@@ -619,7 +621,7 @@ Item 6",
     public void RowRender_Event ()
     {
         var rendered = false;
-        List<string> source = new () { "one", "two", "three" };
+        ObservableCollection<string> source = ["one", "two", "three"];
         var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill () };
         lv.RowRender += (s, _) => rendered = true;
         var top = new Toplevel ();
@@ -636,7 +638,7 @@ Item 6",
     [Fact]
     public void SelectedItem_Get_Set ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.Equal (-1, lv.SelectedItem);
         Assert.Throws<ArgumentException> (() => lv.SelectedItem = 3);
         Exception exception = Record.Exception (() => lv.SelectedItem = -1);
@@ -646,32 +648,35 @@ Item 6",
     [Fact]
     public void SetSource_Preserves_ListWrapper_Instance_If_Not_Null ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two"]) };
 
         Assert.NotNull (lv.Source);
 
-        lv.SetSource (null);
+        lv.SetSource<string> (null);
         Assert.NotNull (lv.Source);
 
         lv.Source = null;
         Assert.Null (lv.Source);
 
-        lv = new() { Source = new ListWrapper (new List<string> { "One", "Two" }) };
+        lv = new () { Source = new ListWrapper<string> (["One", "Two"]) };
         Assert.NotNull (lv.Source);
 
-        lv.SetSourceAsync (null);
+        lv.SetSourceAsync<string> (null);
         Assert.NotNull (lv.Source);
     }
 
     [Fact]
     public void SettingEmptyKeybindingThrows ()
     {
-        var lv = new ListView { Source = new ListWrapper (new List<string> { "One", "Two", "Three" }) };
+        var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
         Assert.Throws<ArgumentException> (() => lv.KeyBindings.Add (Key.Space));
     }
 
     private class NewListDataSource : IListDataSource
     {
+        /// <inheritdoc />
+        public event NotifyCollectionChangedEventHandler CollectionChanged;
+
         public int Count => 0;
         public int Length => 0;
         public bool IsMarked (int item) { throw new NotImplementedException (); }
@@ -692,6 +697,11 @@ Item 6",
 
         public void SetMark (int item, bool value) { throw new NotImplementedException (); }
         public IList ToList () { return new List<string> { "One", "Two", "Three" }; }
+
+        public void Dispose ()
+        {
+            throw new NotImplementedException ();
+        }
     }
 
     [Fact]
@@ -706,7 +716,7 @@ Item 6",
             Width = 7,
             BorderStyle = LineStyle.Single
         };
-        lv.SetSource (new List<string> { "One", "Two", "Three", "Four" });
+        lv.SetSource (["One", "Two", "Three", "Four"]);
         lv.SelectedItemChanged += (s, e) => selected = e.Value.ToString ();
         var top = new Toplevel ();
         top.Add (lv);
@@ -767,9 +777,9 @@ Item 6",
     [AutoInitShutdown]
     public void LeftItem_TopItem_Tests ()
     {
-        List<string> source = new List<string> ();
+        ObservableCollection<string> source = [];
 
-        for (var i = 0; i < 5; i++)
+        for (int i = 0; i < 5; i++)
         {
             source.Add ($"Item {i}");
         }
@@ -779,7 +789,7 @@ Item 6",
             X = 1,
             Width = 10,
             Height = 5,
-            Source = new ListWrapper (source)
+            Source = new ListWrapper<string> (source)
         };
         var top = new Toplevel ();
         top.Add (lv);
@@ -807,4 +817,262 @@ Item 6",
                                                       output);
         top.Dispose ();
     }
-}
+
+    [Fact]
+    public void CollectionChanged_Event ()
+    {
+        var added = 0;
+        var removed = 0;
+        ObservableCollection<string> source = [];
+        var lv = new ListView { Source = new ListWrapper<string> (source) };
+
+        lv.CollectionChanged += (sender, args) =>
+                                {
+                                    if (args.Action == NotifyCollectionChangedAction.Add)
+                                    {
+                                        added++;
+                                    }
+                                    else if (args.Action == NotifyCollectionChangedAction.Remove)
+                                    {
+                                        removed++;
+                                    }
+                                };
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Remove (source [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Empty (source);
+    }
+
+    [Fact]
+    public void CollectionChanged_Event_Is_Only_Subscribed_Once ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        IList<string> source1 = [];
+        var lv = new ListView { Source = new ListWrapper<string> (new ( source1)) };
+
+        lv.CollectionChanged += (sender, args) =>
+                                {
+                                    if (args.Action == NotifyCollectionChangedAction.Add)
+                                    {
+                                        added++;
+                                    }
+                                    else if (args.Action == NotifyCollectionChangedAction.Remove)
+                                    {
+                                        removed++;
+                                    }
+                                    else
+                                    {
+                                        otherActions++;
+                                    }
+                                };
+
+        ObservableCollection<string> source2 = [];
+        lv.Source = new ListWrapper<string> (source2);
+        ObservableCollection<string> source3 = [];
+        lv.Source = new ListWrapper<string> (source3);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+            source2.Add ($"Item{i}");
+            source3.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+            source2.Remove (source2 [0]);
+            source3.Remove (source3 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+        Assert.Empty (source2);
+        Assert.Empty (source3);
+    }
+
+    [Fact]
+    public void CollectionChanged_Event_UnSubscribe_Previous_If_New_Is_Null ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        var lv = new ListView { Source = new ListWrapper<string> (source1) };
+
+        lv.CollectionChanged += (sender, args) =>
+        {
+            if (args.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (args.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        };
+
+        lv.Source = new ListWrapper<string> (null);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+    }
+
+    [Fact]
+    public void ListWrapper_CollectionChanged_Event_Is_Only_Subscribed_Once ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        ListWrapper<string> lw = new (source1);
+
+        lw.CollectionChanged += (sender, args) =>
+        {
+            if (args.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (args.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        };
+
+        ObservableCollection<string> source2 = [];
+        lw = new (source2);
+        ObservableCollection<string> source3 = [];
+        lw = new (source3);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+            source2.Add ($"Item{i}");
+            source3.Add ($"Item{i}");
+        }
+
+        Assert.Equal (3, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        added = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+            source2.Remove (source2 [0]);
+            source3.Remove (source3 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+        Assert.Empty (source2);
+        Assert.Empty (source3);
+    }
+
+    [Fact]
+    public void ListWrapper_CollectionChanged_Event_UnSubscribe_Previous_Is_Disposed ()
+    {
+        var added = 0;
+        var removed = 0;
+        var otherActions = 0;
+        ObservableCollection<string> source1 = [];
+        ListWrapper<string> lw = new (source1);
+
+        lw.CollectionChanged += Lw_CollectionChanged;
+
+        lw.Dispose ();
+        lw = new (null);
+        Assert.Equal (0, lw.Count);
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+
+        for (int i = 0; i < 3; i++)
+        {
+            source1.Remove (source1 [0]);
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (0, removed);
+        Assert.Equal (0, otherActions);
+        Assert.Empty (source1);
+
+
+        void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+            else if (e.Action == NotifyCollectionChangedAction.Remove)
+            {
+                removed++;
+            }
+            else
+            {
+                otherActions++;
+            }
+        }
+    }
+}

+ 6 - 5
UnitTests/Views/ScrollBarViewTests.cs

@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.Collections.ObjectModel;
+using System.Reflection;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
@@ -381,7 +382,7 @@ This is a tes
 
                                               var win = new Window { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
 
-                                              List<string> source = new ();
+                                              ObservableCollection<string> source = [];
 
                                               for (var i = 0; i < 50; i++)
                                               {
@@ -402,7 +403,7 @@ This is a tes
                                                   Y = 0,
                                                   Width = Dim.Fill (),
                                                   Height = Dim.Fill (),
-                                                  Source = new ListWrapper (source)
+                                                  Source = new ListWrapper<string> (source)
                                               };
                                               win.Add (listView);
 
@@ -469,7 +470,7 @@ This is a tes
                                                     Application.Init (new FakeDriver ());
                                                     Toplevel top = new ();
                                                     var win = new Window { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
-                                                    List<string> source = new ();
+                                                    ObservableCollection<string> source = [];
 
                                                     for (var i = 0; i < 50; i++)
                                                     {
@@ -482,7 +483,7 @@ This is a tes
                                                         Y = 0,
                                                         Width = Dim.Fill (),
                                                         Height = Dim.Fill (),
-                                                        Source = new ListWrapper (source)
+                                                        Source = new ListWrapper<string> (source)
                                                     };
                                                     win.Add (listView);
                                                     var newScrollBarView = new ScrollBarView (listView, true, false) { KeepContentAlwaysInViewport = true };