diff --git a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs index c8ad9bd88b..455700af1f 100644 --- a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs +++ b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs @@ -67,6 +67,8 @@ namespace Avalonia.Controls.Selection var previousSource = Source; var previousWritableSelectedItems = _writableSelectedItems; + base.OnSourceCollectionChangeStarted(); + try { _skipSyncFromSelectedItems = true; @@ -81,8 +83,20 @@ namespace Avalonia.Controls.Selection // We skipped the sync from WritableSelectedItems before; do it now that both // the source and WritableSelectedItems are updated. - if (previousSource != Source || previousWritableSelectedItems != _writableSelectedItems) + if (previousWritableSelectedItems != _writableSelectedItems) + { + base.OnSourceCollectionChangeFinished(); + SyncFromSelectedItems(); + } + else if (previousSource != Source) + { SyncFromSelectedItems(); + base.OnSourceCollectionChangeFinished(); + } + else + { + base.OnSourceCollectionChangeFinished(); + } } private protected override void SetSource(IEnumerable? value) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 9d2ffb1fa2..51745e1687 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2160,6 +2160,45 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Does_Not_Write_To_Bound_SelectedItem_When_DataContext_Changes() + { + // Issue #9438. + var vm1 = new SelectionViewModel(); + vm1.Items.Add("foo"); + vm1.Items.Add("bar"); + vm1.SelectedItem = "bar"; + + var vm2 = new SelectionViewModel(); + vm2.Items.Add("foo"); + vm2.Items.Add("bar"); + vm2.SelectedItem = "bar"; + + var target = new SelectingItemsControl + { + DataContext = vm1, + [!ItemsControl.ItemsSourceProperty] = new Binding("Items"), + [!SelectingItemsControl.SelectedItemProperty] = new Binding("SelectedItem"), + Template = Template(), + }; + + Assert.Equal("bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + + var selectedItemChangedRaised = 0; + vm2.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(vm2.SelectedItem)) + { + ++selectedItemChangedRaised; + } + }; + + target.DataContext = vm2; + + Assert.Equal(0, selectedItemChangedRaised); + } + private static IDisposable Start() { return UnitTestApplication.Start(TestServices.StyledWindow); @@ -2238,6 +2277,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private class SelectionViewModel : NotifyingBase { private int _selectedIndex = -1; + private object _selectedItem; public SelectionViewModel() { @@ -2255,6 +2295,16 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + public object SelectedItem + { + get => _selectedItem; + set + { + _selectedItem = value; + RaisePropertyChanged(); + } + } + public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } } @@ -2267,6 +2317,9 @@ namespace Avalonia.Controls.UnitTests.Primitives private class TestSelector : SelectingItemsControl { + public new static readonly DirectProperty SelectedItemsProperty = + SelectingItemsControl.SelectedItemsProperty; + public TestSelector() {