From b8d8fda4eab0313fe26c5c3b574673d2b5c12521 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 24 Sep 2024 23:24:21 +0200 Subject: [PATCH] Prevent AutoCompleteBox getting stuck in a state where it can't drop 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. --- .../AutoCompleteBox/AutoCompleteBox.cs | 103 +++++++++--------- .../AutoCompleteBoxTests.cs | 25 +++++ 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 778fc032a9e..95ea8f9ece2 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -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 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(); - - // 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 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(); + + // 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; + } } /// diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 88303df08ec..5cc3901cd74 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -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); + }); + } + /// /// Retrieves a defined predicate filter through a new AutoCompleteBox /// control instance.