diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index ebf66164b7..a6cf363492 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -1,4 +1,4 @@ -// (c) Copyright Microsoft Corporation. +// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. @@ -165,6 +165,16 @@ namespace Avalonia.Controls /// private bool _allowWrite; + /// + /// A boolean indicating if a cancellation was requested + /// + private bool _cancelRequested; + + /// + /// A boolean indicating if filtering is in action + /// + private bool _filterInAction; + /// /// The TextBox template part. /// @@ -1379,6 +1389,15 @@ namespace Avalonia.Controls /// private void RefreshView() { + // If we have a running filter, trigger a request first + if (_filterInAction) + { + _cancelRequested = true; + } + + // Indicate that filtering is ongoing + _filterInAction = true; + if (_items == null) { ClearView(); @@ -1391,79 +1410,65 @@ namespace Avalonia.Controls // Determine if any filtering mode is on bool stringFiltering = TextFilter != null; bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null; - - int view_index = 0; - int view_count = _view!.Count; + List items = _items; + + // cache properties + var textFilter = TextFilter; + var itemFilter = ItemFilter; + var _newViewItems = new Collection(); + + // if the mode is objectFiltering and itemFilter is null, we throw an exception + if (objectFiltering && itemFilter is null) + { + // indicate that filtering is not ongoing anymore + _filterInAction = false; + _cancelRequested = false; + + throw new Exception( + "ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom"); + } + foreach (object item in items) { + // Exit the fitter when requested if cancellation is requested + if (_cancelRequested) + { + return; + } + bool inResults = !(stringFiltering || objectFiltering); + if (!inResults) { if (stringFiltering) { - inResults = TextFilter!(text, FormatValue(item)); + inResults = textFilter!(text, FormatValue(item)); } - else + else if (objectFiltering) { - if (ItemFilter is null) - { - throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom"); - } - else - { - inResults = ItemFilter(text, item); - } + inResults = itemFilter!(text, item); } } - if (view_count > view_index && inResults && _view[view_index] == item) + if (inResults) { - // Item is still in the view - view_index++; - } - else if (inResults) - { - // Insert the item - if (view_count > view_index && _view[view_index] != item) - { - // Replace item - // Unfortunately replacing via index throws a fatal - // exception: View[view_index] = item; - // Cost: O(n) vs O(1) - _view.RemoveAt(view_index); - _view.Insert(view_index, item); - view_index++; - } - else - { - // Add the item - if (view_index == view_count) - { - // Constant time is preferred (Add). - _view.Add(item); - } - else - { - _view.Insert(view_index, item); - } - view_index++; - view_count++; - } - } - else if (view_count > view_index && _view[view_index] == item) - { - // Remove the item - _view.RemoveAt(view_index); - view_count--; + _newViewItems.Add(item); } } + _view?.Clear(); + _view?.AddRange(_newViewItems); + // Clear the evaluator to discard a reference to the last item if (_valueBindingEvaluator != null) { _valueBindingEvaluator.ClearDataContext(); } + + // indicate that filtering is not ongoing anymore + _filterInAction = false; + _cancelRequested = false; } ///