From 888818a16fc7bea36a4e62f36ce67e53398f461e Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 26 Jul 2023 15:26:37 +0200 Subject: [PATCH] Make filtering async and cancelable --- .../AutoCompleteBox/AutoCompleteBox.cs | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index ebf66164b7..2e63383030 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. /// @@ -1377,8 +1387,17 @@ namespace Avalonia.Controls /// Walks through the items enumeration. Performance is not going to be /// perfect with the current implementation. /// - private void RefreshView() + private async 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,72 +1410,60 @@ 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; - foreach (object item in items) + + // cache properties + var textFilter = TextFilter; + var itemFilter = ItemFilter; + var _newViewItems = new Collection(); + + bool success = false; + + await Task.Run(() => { - bool inResults = !(stringFiltering || objectFiltering); - if (!inResults) + foreach (object item in items) { - if (stringFiltering) + // Exit the fitter when requested if cancellation is requested + if (_cancelRequested) { - inResults = TextFilter!(text, FormatValue(item)); + return; } - else + + bool inResults = !(stringFiltering || objectFiltering); + + if (!inResults) { - if (ItemFilter is null) + if (stringFiltering) { - throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom"); + inResults = textFilter!(text, FormatValue(item)); } else { - inResults = ItemFilter(text, item); + if (itemFilter is null) + { + throw new Exception( + "ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom"); + } + inResults = itemFilter(text, item); } } - } - if (view_count > view_index && inResults && _view[view_index] == item) - { - // Item is still in the view - view_index++; - } - else if (inResults) - { - // Insert the item - if (view_count > view_index && _view[view_index] != item) + if (inResults) { - // 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++; + _newViewItems.Add(item); } } - else if (view_count > view_index && _view[view_index] == item) - { - // Remove the item - _view.RemoveAt(view_index); - view_count--; - } + + // set success to true if we reached the end of the loop + success = true; + }); + + // add new view items if filtering was successful + if (success && _view != null) + { + _view.Clear(); + _view.AddRange(_newViewItems); } // Clear the evaluator to discard a reference to the last item @@ -1464,6 +1471,10 @@ namespace Avalonia.Controls { _valueBindingEvaluator.ClearDataContext(); } + + // indicate that filtering is not ongoing anymore + _filterInAction = false; + _cancelRequested = false; } ///