Browse Source

Make filtering async and cancelable

pull/12338/head
Tim 3 years ago
parent
commit
888818a16f
  1. 115
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

115
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
/// </summary>
private bool _allowWrite;
/// <summary>
/// A boolean indicating if a cancellation was requested
/// </summary>
private bool _cancelRequested;
/// <summary>
/// A boolean indicating if filtering is in action
/// </summary>
private bool _filterInAction;
/// <summary>
/// The TextBox template part.
/// </summary>
@ -1377,8 +1387,17 @@ namespace Avalonia.Controls
/// Walks through the items enumeration. Performance is not going to be
/// perfect with the current implementation.
/// </summary>
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<object> items = _items;
foreach (object item in items)
// cache properties
var textFilter = TextFilter;
var itemFilter = ItemFilter;
var _newViewItems = new Collection<object>();
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;
}
/// <summary>

Loading…
Cancel
Save