diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 36af3853e..a5d3ed4a2 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -187,7 +187,7 @@ public class ComboBox : View { SelectedItem = -1; _search.Text = string.Empty; - Search_Changed (this, new StateEventArgs (string.Empty, _search.Text)); + Search_Changed (this, new StateEventArgs (string.Empty, _search.Text)); SetNeedsDisplay (); } } @@ -645,7 +645,9 @@ public class ComboBox : View private void ResetSearchSet (bool noCopy = false) { + _listview.SuspendCollectionChangedEvent (); _searchSet.Clear (); + _listview.ResumeSuspendCollectionChangedEvent (); if (_autoHide || noCopy) { @@ -682,6 +684,8 @@ public class ComboBox : View if (!string.IsNullOrEmpty (_search.Text)) { + _listview.SuspendCollectionChangedEvent (); + foreach (object item in _source.ToList ()) { // Iterate to preserver object type and force deep copy @@ -694,6 +698,8 @@ public class ComboBox : View _searchSet.Add (item); } } + + _listview.ResumeSuspendCollectionChangedEvent (); } } @@ -738,11 +744,16 @@ public class ComboBox : View return; } + // PERF: At the request of @dodexahedron in the comment https://github.com/gui-cs/Terminal.Gui/pull/3552#discussion_r1648112410. + _listview.SuspendCollectionChangedEvent (); + // force deep copy foreach (object item in Source.ToList ()) { _searchSet.Add (item); } + + _listview.ResumeSuspendCollectionChangedEvent (); } private void SetSearchText (string value) { _search.Text = _text = value; } @@ -765,8 +776,11 @@ public class ComboBox : View /// Consider making public private void ShowList () { + _listview.SuspendCollectionChangedEvent (); _listview.SetSource (_searchSet); - _listview.Clear (); + _listview.ResumeSuspendCollectionChangedEvent (); + + _listview.Clear (); _listview.Height = CalculatetHeight (); SuperView?.BringSubviewToFront (this); } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 296cb2717..5701c3a8b 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -19,6 +19,12 @@ public interface IListDataSource: IDisposable /// Returns the maximum length of elements to display int Length { get; } + /// + /// Allow suspending the event from being invoked, + /// if , otherwise is . + /// + bool SuspendCollectionChangedEvent { get; set; } + /// Should return whether the specified item is currently marked. /// , if marked, otherwise. /// Item index. @@ -893,6 +899,28 @@ public class ListView : View base.Dispose (disposing); } + + /// + /// Allow suspending the event from being invoked, + /// + public void SuspendCollectionChangedEvent () + { + if (Source is { }) + { + Source.SuspendCollectionChangedEvent = true; + } + } + + /// + /// Allow resume the event from being invoked, + /// + public void ResumeSuspendCollectionChangedEvent () + { + if (Source is { }) + { + Source.SuspendCollectionChangedEvent = false; + } + } } /// @@ -920,8 +948,11 @@ public class ListWrapper : IListDataSource, IDisposable private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e) { - CheckAndResizeMarksIfRequired (); - CollectionChanged?.Invoke (sender, e); + if (!SuspendCollectionChangedEvent) + { + CheckAndResizeMarksIfRequired (); + CollectionChanged?.Invoke (sender, e); + } } /// @@ -933,7 +964,24 @@ public class ListWrapper : IListDataSource, IDisposable /// public int Length { get; private set; } - void CheckAndResizeMarksIfRequired () + private bool _suspendCollectionChangedEvent; + + /// + public bool SuspendCollectionChangedEvent + { + get => _suspendCollectionChangedEvent; + set + { + _suspendCollectionChangedEvent = value; + + if (!_suspendCollectionChangedEvent) + { + CheckAndResizeMarksIfRequired (); + } + } + } + + private void CheckAndResizeMarksIfRequired () { if (_source != null && _count != _source.Count) { diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index 83ce63d4a..a11d6950d 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -207,6 +207,7 @@ public class ListViewWithSelection : Scenario public event NotifyCollectionChangedEventHandler CollectionChanged; public int Count => Scenarios != null ? Scenarios.Count : 0; public int Length { get; private set; } + public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); } public void Render ( ListView container, diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 3f222b1dc..5f157a81d 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -679,6 +679,9 @@ Item 6", public int Count => 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 void Render ( @@ -1075,4 +1078,82 @@ Item 6", } } } + + [Fact] + public void ListWrapper_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests () + { + var added = 0; + ObservableCollection source = []; + ListWrapper 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 source = []; + ListView lv = new ListView { Source = new ListWrapper (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++; + } + } + } } \ No newline at end of file