From 5cf6662f741cb0b9c1bc5cefc4ebe5f058aa8135 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 17:25:15 +0200 Subject: [PATCH 1/3] Bind ListBox.SelectedItems again. Was removed accidentally. --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 47b4ce7151..f4d81418ac 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,7 +10,13 @@ HorizontalAlignment="Center" Spacing="16"> - + From 73a2637eed83f082be19a7d136aa5a0da164e1aa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:04:47 +0200 Subject: [PATCH 2/3] Added failing test for removing selected item with BeginInit. --- .../Primitives/SelectingItemsControlTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index fe9c7b1261..9ef2750ff3 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); target.SelectedIndex = 1; Assert.Equal(items[1], target.SelectedItem); @@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.NotNull(receivedArgs); Assert.Empty(receivedArgs.AddedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); + } + + [Fact] + public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit() + { + var items = new AvaloniaList + { + new Item(), + new Item(), + }; + + var target = new SelectingItemsControl(); + target.BeginInit(); + target.Items = items; + target.Template = Template(); + target.EndInit(); + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + Assert.Equal(items[0], target.SelectedItem); + Assert.Equal(0, target.SelectedIndex); + + SelectionChangedEventArgs receivedArgs = null; + + target.SelectionChanged += (_, args) => receivedArgs = args; + + var removed = items[0]; + + items.RemoveAt(0); + + Assert.Null(target.SelectedItem); + Assert.Equal(-1, target.SelectedIndex); + Assert.NotNull(receivedArgs); + Assert.Empty(receivedArgs.AddedItems); + Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); } [Fact] From 6555a51f5ae6986c17ad2da3f767e467712d612a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:37:23 +0200 Subject: [PATCH 3/3] Fix selection after deleting an item. `SelectionModel` needs to subscribe to `CollectionChanged` on the items before `ItemsControl` in order for the selection to be correct when we come to setting the selected state. Because `SelectionModel.Source` isn't subscribed during initialization in `ItemsChanged`, we also need to make sure we don't subscribe `ItemsControl` to the collection changes during initialization. Instead subscribe in `OnInitialized` (this requires a few tests to be rooted in order to be called). Fixes #4293 --- src/Avalonia.Controls/ItemsControl.cs | 11 +++++++++-- tests/Avalonia.Controls.UnitTests/CarouselTests.cs | 3 +++ .../Avalonia.Controls.UnitTests/ItemsControlTests.cs | 4 ++++ .../Primitives/SelectingItemsControlTests.cs | 4 ++++ .../Primitives/SelectingItemsControlTests_Multiple.cs | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 6e0ad66699..da9f619932 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -70,7 +70,6 @@ namespace Avalonia.Controls public ItemsControl() { PseudoClasses.Add(":empty"); - SubscribeToItems(_items); } /// @@ -265,6 +264,11 @@ namespace Avalonia.Controls { } + protected override void OnInitialized() + { + SubscribeToItems(_items); + } + /// /// Handles directional navigation within the . /// @@ -330,7 +334,10 @@ namespace Avalonia.Controls Presenter.Items = newValue; } - SubscribeToItems(newValue); + if (IsInitialized) + { + SubscribeToItems(newValue); + } } /// diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index a292910fae..c6ca0fb2bf 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; +using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -155,6 +156,7 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -247,6 +249,7 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 684486cbae..faaa3ed063 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -131,6 +131,7 @@ namespace Avalonia.Controls.UnitTests var child = new Control(); var items = new AvaloniaList(child); + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; items.RemoveAt(0); @@ -283,6 +284,7 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo" }; var called = false; + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -303,6 +305,7 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo", "Bar" }; var called = false; + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -376,6 +379,7 @@ namespace Avalonia.Controls.UnitTests Items = new[] { 1, 2, 3 }, }; + var root = new TestRoot(target); Assert.DoesNotContain(":empty", target.Classes); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 9ef2750ff3..e43e855ae0 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -170,6 +170,8 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected }; + var root = new TestRoot(listBox); + listBox.BeginInit(); listBox.SelectedIndex = 1; @@ -480,6 +482,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new Item { IsSelected = true }); @@ -919,6 +922,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Items = items, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index dcf25beb50..e9ec8d114f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -1014,6 +1014,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1043,6 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1076,6 +1078,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1199,6 +1202,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new ItemContainer { IsSelected = true });