diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index bd35b68830..71e5380624 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -798,6 +798,9 @@ namespace Avalonia.Controls.Primitives UpdateContainerSelection(); + _selectedItemsSync ??= new SelectedItemsSync(model); + _selectedItemsSync.SelectionModel = model; + if (SelectedIndex != -1) { RaiseEvent(new SelectionChangedEventArgs( diff --git a/src/Avalonia.Controls/Utils/SelectedItemsSync.cs b/src/Avalonia.Controls/Utils/SelectedItemsSync.cs index 472ec800b9..7f54f25e97 100644 --- a/src/Avalonia.Controls/Utils/SelectedItemsSync.cs +++ b/src/Avalonia.Controls/Utils/SelectedItemsSync.cs @@ -34,11 +34,14 @@ namespace Avalonia.Controls.Utils get => _selectionModel; set { - value = value ?? throw new ArgumentNullException(nameof(value)); - UnsubscribeFromSelectionModel(_selectionModel); - _selectionModel = value; - SubscribeToSelectionModel(_selectionModel); - SyncSelectedItemsWithSelectionModel(); + if (_selectionModel != value) + { + value = value ?? throw new ArgumentNullException(nameof(value)); + UnsubscribeFromSelectionModel(_selectionModel); + _selectionModel = value; + SubscribeToSelectionModel(_selectionModel); + SyncSelectedItemsWithSelectionModel(); + } } } @@ -49,16 +52,19 @@ namespace Avalonia.Controls.Utils { value ??= new AvaloniaList(); - if (value.IsFixedSize) + if (_selectedItems != value) { - throw new NotSupportedException( - "Cannot assign fixed size selection to SelectedItems."); - } + if (value.IsFixedSize) + { + throw new NotSupportedException( + "Cannot assign fixed size selection to SelectedItems."); + } - UnsubscribeFromSelectedItems(_selectedItems); - _selectedItems = value; - SubscribeToSelectedItems(_selectedItems); - SyncSelectionModelWithSelectedItems(); + UnsubscribeFromSelectedItems(_selectedItems); + _selectedItems = value; + SubscribeToSelectedItems(_selectedItems); + SyncSelectionModelWithSelectedItems(); + } } } @@ -226,6 +232,12 @@ namespace Avalonia.Controls.Utils } } + private void SelectionModelSourceReset(object sender, EventArgs e) + { + SyncSelectionModelWithSelectedItems(); + } + + private void SubscribeToSelectedItems(IList selectedItems) { if (selectedItems is INotifyCollectionChanged incc) @@ -238,6 +250,7 @@ namespace Avalonia.Controls.Utils { model.PropertyChanged += SelectionModelPropertyChanged; model.SelectionChanged += SelectionModelSelectionChanged; + model.SourceReset += SelectionModelSourceReset; } private void UnsubscribeFromSelectedItems(IList selectedItems) @@ -252,6 +265,7 @@ namespace Avalonia.Controls.Utils { model.PropertyChanged -= SelectionModelPropertyChanged; model.SelectionChanged -= SelectionModelSelectionChanged; + model.SourceReset -= SelectionModelSourceReset; } private static int IndexOf(object? source, object? item) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs index 04aaa11807..3899d9dfbf 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using Avalonia.Collections; using Avalonia.Controls.Selection; using Avalonia.Controls.Utils; @@ -230,6 +231,19 @@ namespace Avalonia.Controls.UnitTests.Utils Assert.Equal(1, model.SelectedIndex); } + [Fact] + public void Restores_Selection_On_Items_Reset() + { + var items = new ResettingCollection(new[] { "foo", "bar", "baz" }); + var model = new SelectionModel { Source = items }; + var target = new SelectedItemsSync(model); + + model.SelectedIndex = 1; + items.Reset(new[] { "baz", "foo", "bar" }); + + Assert.Equal(2, model.SelectedIndex); + } + private static SelectedItemsSync CreateTarget( IEnumerable items = null) { @@ -241,5 +255,24 @@ namespace Avalonia.Controls.UnitTests.Utils var target = new SelectedItemsSync(model); return target; } + + private class ResettingCollection : List, INotifyCollectionChanged + { + public ResettingCollection(IEnumerable items) + { + AddRange(items); + } + + public void Reset(IEnumerable items) + { + Clear(); + AddRange(items); + CollectionChanged?.Invoke( + this, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + } } }