فهرست منبع

Fixes #3551. v2: Big performance impact on ListView/ComboBox's that contain a lot of items

BDisp 1 سال پیش
والد
کامیت
48c65bdfa1
4فایلهای تغییر یافته به همراه148 افزوده شده و 5 حذف شده
  1. 15 2
      Terminal.Gui/Views/ComboBox.cs
  2. 51 3
      Terminal.Gui/Views/ListView.cs
  3. 1 0
      UICatalog/Scenarios/ListViewWithSelection.cs
  4. 81 0
      UnitTests/Views/ListViewTests.cs

+ 15 - 2
Terminal.Gui/Views/ComboBox.cs

@@ -187,7 +187,7 @@ public class ComboBox : View
             {
             {
                 SelectedItem = -1;
                 SelectedItem = -1;
                 _search.Text = string.Empty;
                 _search.Text = string.Empty;
-                Search_Changed (this, new StateEventArgs<string> (string.Empty, _search.Text)); 
+                Search_Changed (this, new StateEventArgs<string> (string.Empty, _search.Text));
                 SetNeedsDisplay ();
                 SetNeedsDisplay ();
             }
             }
         }
         }
@@ -645,7 +645,9 @@ public class ComboBox : View
 
 
     private void ResetSearchSet (bool noCopy = false)
     private void ResetSearchSet (bool noCopy = false)
     {
     {
+        _listview.SuspendCollectionChangedEvent ();
         _searchSet.Clear ();
         _searchSet.Clear ();
+        _listview.ResumeSuspendCollectionChangedEvent ();
 
 
         if (_autoHide || noCopy)
         if (_autoHide || noCopy)
         {
         {
@@ -682,6 +684,8 @@ public class ComboBox : View
 
 
             if (!string.IsNullOrEmpty (_search.Text))
             if (!string.IsNullOrEmpty (_search.Text))
             {
             {
+                _listview.SuspendCollectionChangedEvent ();
+
                 foreach (object item in _source.ToList ())
                 foreach (object item in _source.ToList ())
                 {
                 {
                     // Iterate to preserver object type and force deep copy
                     // Iterate to preserver object type and force deep copy
@@ -694,6 +698,8 @@ public class ComboBox : View
                         _searchSet.Add (item);
                         _searchSet.Add (item);
                     }
                     }
                 }
                 }
+
+                _listview.ResumeSuspendCollectionChangedEvent ();
             }
             }
         }
         }
 
 
@@ -738,11 +744,15 @@ public class ComboBox : View
             return;
             return;
         }
         }
 
 
+        _listview.SuspendCollectionChangedEvent ();
+
         // force deep copy
         // force deep copy
         foreach (object item in Source.ToList ())
         foreach (object item in Source.ToList ())
         {
         {
             _searchSet.Add (item);
             _searchSet.Add (item);
         }
         }
+
+        _listview.ResumeSuspendCollectionChangedEvent ();
     }
     }
 
 
     private void SetSearchText (string value) { _search.Text = _text = value; }
     private void SetSearchText (string value) { _search.Text = _text = value; }
@@ -765,8 +775,11 @@ public class ComboBox : View
     /// Consider making public
     /// Consider making public
     private void ShowList ()
     private void ShowList ()
     {
     {
+        _listview.SuspendCollectionChangedEvent ();
         _listview.SetSource (_searchSet);
         _listview.SetSource (_searchSet);
-        _listview.Clear (); 
+        _listview.ResumeSuspendCollectionChangedEvent ();
+
+        _listview.Clear ();
         _listview.Height = CalculatetHeight ();
         _listview.Height = CalculatetHeight ();
         SuperView?.BringSubviewToFront (this);
         SuperView?.BringSubviewToFront (this);
     }
     }

+ 51 - 3
Terminal.Gui/Views/ListView.cs

@@ -19,6 +19,12 @@ public interface IListDataSource: IDisposable
     /// <summary>Returns the maximum length of elements to display</summary>
     /// <summary>Returns the maximum length of elements to display</summary>
     int Length { get; }
     int Length { get; }
 
 
+    /// <summary>
+    /// Allow suspending the <see cref="CollectionChanged"/> event from being invoked,
+    /// if <see langword="true"/>, otherwise is <see langword="false"/>.
+    /// </summary>
+    bool SuspendCollectionChangedEvent { get; set; }
+
     /// <summary>Should return whether the specified item is currently marked.</summary>
     /// <summary>Should return whether the specified item is currently marked.</summary>
     /// <returns><see langword="true"/>, if marked, <see langword="false"/> otherwise.</returns>
     /// <returns><see langword="true"/>, if marked, <see langword="false"/> otherwise.</returns>
     /// <param name="item">Item index.</param>
     /// <param name="item">Item index.</param>
@@ -893,6 +899,28 @@ public class ListView : View
 
 
         base.Dispose (disposing);
         base.Dispose (disposing);
     }
     }
+
+    /// <summary>
+    /// Allow suspending the <see cref="CollectionChanged"/> event from being invoked,
+    /// </summary>
+    public void SuspendCollectionChangedEvent ()
+    {
+        if (Source is { })
+        {
+            Source.SuspendCollectionChangedEvent = true;
+        }
+    }
+
+    /// <summary>
+    /// Allow resume the <see cref="CollectionChanged"/> event from being invoked,
+    /// </summary>
+    public void ResumeSuspendCollectionChangedEvent ()
+    {
+        if (Source is { })
+        {
+            Source.SuspendCollectionChangedEvent = false;
+        }
+    }
 }
 }
 
 
 /// <summary>
 /// <summary>
@@ -920,8 +948,11 @@ public class ListWrapper<T> : IListDataSource, IDisposable
 
 
     private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
     private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
     {
     {
-        CheckAndResizeMarksIfRequired ();
-        CollectionChanged?.Invoke (sender, e);
+        if (!SuspendCollectionChangedEvent)
+        {
+            CheckAndResizeMarksIfRequired ();
+            CollectionChanged?.Invoke (sender, e);
+        }
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
@@ -933,7 +964,24 @@ public class ListWrapper<T> : IListDataSource, IDisposable
     /// <inheritdoc/>
     /// <inheritdoc/>
     public int Length { get; private set; }
     public int Length { get; private set; }
 
 
-    void CheckAndResizeMarksIfRequired ()
+    private bool _suspendCollectionChangedEvent;
+
+    /// <inheritdoc />
+    public bool SuspendCollectionChangedEvent
+    {
+        get => _suspendCollectionChangedEvent;
+        set
+        {
+            _suspendCollectionChangedEvent = value;
+
+            if (!_suspendCollectionChangedEvent)
+            {
+                CheckAndResizeMarksIfRequired ();
+            }
+        }
+    }
+
+    private void CheckAndResizeMarksIfRequired ()
     {
     {
         if (_source != null && _count != _source.Count)
         if (_source != null && _count != _source.Count)
         {
         {

+ 1 - 0
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -207,6 +207,7 @@ public class ListViewWithSelection : Scenario
         public event NotifyCollectionChangedEventHandler CollectionChanged;
         public event NotifyCollectionChangedEventHandler CollectionChanged;
         public int Count => Scenarios != null ? Scenarios.Count : 0;
         public int Count => Scenarios != null ? Scenarios.Count : 0;
         public int Length { get; private set; }
         public int Length { get; private set; }
+        public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); }
 
 
         public void Render (
         public void Render (
             ListView container,
             ListView container,

+ 81 - 0
UnitTests/Views/ListViewTests.cs

@@ -679,6 +679,9 @@ Item 6",
 
 
         public int Count => 0;
         public int Count => 0;
         public int Length => 0;
         public int Length => 0;
+
+        public bool SuspendCollectionChangedEvent { get => throw new NotImplementedException (); set => throw new NotImplementedException (); }
+
         public bool IsMarked (int item) { throw new NotImplementedException (); }
         public bool IsMarked (int item) { throw new NotImplementedException (); }
 
 
         public void Render (
         public void Render (
@@ -1075,4 +1078,82 @@ Item 6",
             }
             }
         }
         }
     }
     }
+
+    [Fact]
+    public void ListWrapper_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests ()
+    {
+        var added = 0;
+        ObservableCollection<string> source = [];
+        ListWrapper<string> lw = new (source);
+
+        lw.CollectionChanged += Lw_CollectionChanged;
+
+        lw.SuspendCollectionChangedEvent = true;
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, lw.Count);
+        Assert.Equal (3, source.Count);
+
+        lw.SuspendCollectionChangedEvent = false;
+
+        for (int i = 3; i < 6; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (6, lw.Count);
+        Assert.Equal (6, source.Count);
+
+
+        void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+        }
+    }
+
+    [Fact]
+    public void ListView_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests ()
+    {
+        var added = 0;
+        ObservableCollection<string> source = [];
+        ListView lv = new ListView { Source = new ListWrapper<string> (source) };
+
+        lv.CollectionChanged += Lw_CollectionChanged;
+
+        lv.SuspendCollectionChangedEvent ();
+
+        for (int i = 0; i < 3; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (0, added);
+        Assert.Equal (3, lv.Source.Count);
+        Assert.Equal (3, source.Count);
+
+        lv.ResumeSuspendCollectionChangedEvent ();
+
+        for (int i = 3; i < 6; i++)
+        {
+            source.Add ($"Item{i}");
+        }
+        Assert.Equal (3, added);
+        Assert.Equal (6, lv.Source.Count);
+        Assert.Equal (6, source.Count);
+
+
+        void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action == NotifyCollectionChangedAction.Add)
+            {
+                added++;
+            }
+        }
+    }
 }
 }