Skip to content

Commit

Permalink
Prevent AutoCompleteBox getting stuck in a state where it can't drop …
Browse files Browse the repository at this point in the history
…down. (#17074)

* Added failing AutoCompleteBox test.

* Make sure that flags are reset if exits early.

Wrap everything in a `try`...`finally` block so that they get reset even on exception.
  • Loading branch information
grokys authored Sep 24, 2024
1 parent 33bd02c commit b8d8fda
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 51 deletions.
103 changes: 52 additions & 51 deletions src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1403,74 +1403,75 @@ private void RefreshView()
// Indicate that filtering is ongoing
_filterInAction = true;

if (_items == null)
try
{
ClearView();
return;
}
if (_items == null)
{
ClearView();
return;
}

// Cache the current text value
string text = Text ?? string.Empty;
// Cache the current text value
string text = Text ?? string.Empty;

// Determine if any filtering mode is on
bool stringFiltering = TextFilter != null;
bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;

List<object> items = _items;
// Determine if any filtering mode is on
bool stringFiltering = TextFilter != null;
bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;

// cache properties
var textFilter = TextFilter;
var itemFilter = ItemFilter;
var _newViewItems = new Collection<object>();

// 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");
}
List<object> items = _items;

foreach (object item in items)
{
// Exit the fitter when requested if cancellation is requested
if (_cancelRequested)
// cache properties
var textFilter = TextFilter;
var itemFilter = ItemFilter;
var _newViewItems = new Collection<object>();

// if the mode is objectFiltering and itemFilter is null, we throw an exception
if (objectFiltering && itemFilter is null)
{
return;
throw new Exception(
"ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}

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 if (objectFiltering)

bool inResults = !(stringFiltering || objectFiltering);

if (!inResults)
{
inResults = itemFilter!(text, item);
if (stringFiltering)
{
inResults = textFilter!(text, FormatValue(item));
}
else if (objectFiltering)
{
inResults = itemFilter!(text, item);
}
}
}

if (inResults)
{
_newViewItems.Add(item);
if (inResults)
{
_newViewItems.Add(item);
}
}
}

_view?.Clear();
_view?.AddRange(_newViewItems);

// Clear the evaluator to discard a reference to the last item
_valueBindingEvaluator?.ClearDataContext();
_view?.Clear();
_view?.AddRange(_newViewItems);

// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
// Clear the evaluator to discard a reference to the last item
_valueBindingEvaluator?.ClearDataContext();
}
finally
{
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
}
}

/// <summary>
Expand Down
25 changes: 25 additions & 0 deletions tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,31 @@ public void CaretIndex_Changes()
});
}

[Fact]
public void Attempting_To_Open_Without_Items_Does_Not_Prevent_Future_Opening_With_Items()
{
RunTest((control, textbox) =>
{
// Allow the drop down to open without anything entered.
control.MinimumPrefixLength = 0;
// Clear the items.
var source = control.ItemsSource;
control.ItemsSource = null;
control.IsDropDownOpen = true;
// DropDown was not actually opened because there are no items.
Assert.False(control.IsDropDownOpen);
// Set the items and try to open the drop down again.
control.ItemsSource = source;
control.IsDropDownOpen = true;
// DropDown can now be opened.
Assert.True(control.IsDropDownOpen);
});
}

/// <summary>
/// Retrieves a defined predicate filter through a new AutoCompleteBox
/// control instance.
Expand Down

0 comments on commit b8d8fda

Please sign in to comment.