From 3692960ce5f43d88bd0f9b2b8378b14036de67a2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 8 Feb 2024 05:13:44 +0100 Subject: [PATCH] Fix AlwaysSelected in conjuction with SelectedItem/SelectedIndex binding (#14107) * Added failing tests for #12733. * Clear SkipLostSelection on batch update start. If `Source` is changed during a collection update, then the `Clear()` operation will not be committed immediately due to `_isSourceCollectionChanging` being set. In this case, `update.Operation` will still have `SkipLostSelection == true`, meaning that `LostSelection` will not be raised, causing #12733. Clear the flag manually each time `BeginBatchUpdate` is called to avoid this. Fixes #12733 --------- Co-authored-by: Max Katz --- .../Selection/SelectionModel.cs | 1 + .../Primitives/SelectingItemsControlTests.cs | 19 +++++++++++ .../Selection/SelectionModelTests_Single.cs | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs index 22d4a371e5..818308793b 100644 --- a/src/Avalonia.Controls/Selection/SelectionModel.cs +++ b/src/Avalonia.Controls/Selection/SelectionModel.cs @@ -203,6 +203,7 @@ namespace Avalonia.Controls.Selection public void BeginBatchUpdate() { _operation ??= new Operation(this); + _operation.SkipLostSelection = false; ++_operation.UpdateCount; } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 8c8bf2b33f..1e4711cb0f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2292,6 +2292,25 @@ namespace Avalonia.Controls.UnitTests.Primitives } + [Fact] + public void Changing_DataContext_Respects_AlwaysSelected() + { + // Issue #12733 + var target = new ListBox + { + DataContext = Enumerable.Range(0, 10).ToList(), + SelectionMode = SelectionMode.AlwaysSelected, + Template = Template(), + [!ListBox.ItemsSourceProperty] = new Binding(), + }; + + Assert.Equal(0, target.SelectedIndex); + + target.DataContext = Enumerable.Range(10, 10).ToList(); + + Assert.Equal(0, target.SelectedIndex); + } + [Theory] [MemberData(nameof(GetSelectionFieldPermutationParameters))] public void SelectedItem_And_Selection_Properties_Work_In_Any_Order_When_Initializing(SelectionField[] fields) diff --git a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs index 4b652c68c5..64236268f8 100644 --- a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; @@ -1346,6 +1347,38 @@ namespace Avalonia.Controls.UnitTests.Selection Assert.Equal(0, raised); } + + [Fact] + public void LostSelection_Is_Called_When_Source_Changed_While_CollectionChange_In_Progress() + { + // Issue #12733. + var data1 = new AvaloniaList { "foo1", "bar1", "baz1" }; + var data2 = new AvaloniaList { "foo1", "bar1", "baz1" }; + var target = new DerivedSelectionModel { Source = data1 }; + var raised = 0; + + target.LostSelection += (s, e) => + { + if (target.Source == data2) + { + ++raised; + } + }; + + target.UpdateSource(data2); + + Assert.Equal(1, raised); + } + + private class DerivedSelectionModel : SelectionModel + { + public void UpdateSource(IEnumerable? source) + { + OnSourceCollectionChangeStarted(); + Source = source; + OnSourceCollectionChangeFinished(); + } + } } public class UntypedInterface