From f9a6f5b1ece28eeb4c584b6928fae493a0dd7394 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Apr 2021 09:20:12 +0200 Subject: [PATCH 1/3] Create failing test for SelectedItems binding. --- .../ListBoxTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 963bba7c83..7c57e22933 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Subjects; using Avalonia.Collections; @@ -457,6 +458,33 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Initial_Binding_Of_SelectedItems_Should_Not_Cause_Write_To_SelectedItems() + { + var target = new ListBox + { + [!ListBox.ItemsProperty] = new Binding("Items"), + [!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"), + }; + + var viewModel = new + { + Items = new[] { "Foo", "Bar", "Baz " }, + SelectedItems = new ObservableCollection { "Bar" }, + }; + + var raised = 0; + + viewModel.SelectedItems.CollectionChanged += (s, e) => ++raised; + + target.DataContext = viewModel; + + Assert.Equal(0, raised); + Assert.Equal(new[] { "Bar" }, viewModel.SelectedItems); + Assert.Equal(new[] { "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems); + } + private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate((parent, scope) => From 67a1e0921ca68e578ba44289f3a0ddac4d837d6f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Apr 2021 10:00:21 +0200 Subject: [PATCH 2/3] Don't update SelectedItems when changing source. --- .../Selection/InternalSelectionModel.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs index c9d91b1ae6..d0da2f7ce2 100644 --- a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs +++ b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs @@ -80,10 +80,12 @@ namespace Avalonia.Controls.Selection try { _ignoreSelectedItemsChanges = true; + ++_ignoreModelChanges; base.SetSource(value); } finally { + --_ignoreModelChanges; _ignoreSelectedItemsChanges = false; } @@ -93,17 +95,14 @@ namespace Avalonia.Controls.Selection } else { - foreach (var i in oldSelection) - { - var index = ItemsView!.IndexOf(i); - Select(index); - } + SyncFromSelectedItems(); } } private void SyncToSelectedItems() { - if (_writableSelectedItems is object) + if (_writableSelectedItems is object && + !SequenceEqual(_writableSelectedItems, base.SelectedItems)) { try { @@ -310,5 +309,27 @@ namespace Avalonia.Controls.Selection return -1; } + + private static bool SequenceEqual(IList first, IReadOnlyList second) + { + if (first is IEnumerable e) + { + return e.SequenceEqual(second); + } + + var comparer = EqualityComparer.Default; + var e1 = first.GetEnumerator(); + using var e2 = second.GetEnumerator(); + + while (e1.MoveNext()) + { + if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current))) + { + return false; + } + } + + return !e2.MoveNext(); + } } } From eeb7c8d921fba166276e0b24c4509ed864c7a378 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Apr 2021 13:26:51 +0200 Subject: [PATCH 3/3] Reset _isResetting, d'oh. --- src/Avalonia.Controls/Selection/InternalSelectionModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs index d0da2f7ce2..73c0184ebb 100644 --- a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs +++ b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs @@ -223,6 +223,7 @@ namespace Avalonia.Controls.Selection if (_isResetting) { --_ignoreModelChanges; + _isResetting = false; } }