From 0a6987b0855bc7cd0b371571bb0234dc779da181 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Dec 2023 07:17:02 +0100 Subject: [PATCH] Hackfix for calling SelectAll with SelectedItem binding (#13868) * Added failing tests for #13676. * Add hackfix for #13676. --------- Co-authored-by: Max Katz --- .../Selection/SelectionModel.cs | 14 ++++- .../ListBoxTests_Multiple.cs | 59 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs index 5402499fcf..22d4a371e5 100644 --- a/src/Avalonia.Controls/Selection/SelectionModel.cs +++ b/src/Avalonia.Controls/Selection/SelectionModel.cs @@ -69,6 +69,18 @@ namespace Avalonia.Controls.Selection get => _selectedIndex; set { + if (_operation is not null && _operation.UpdateCount == 0) + { + // An operation is in the process of being committed. In this case, if the new + // value for SelectedIndex is unchanged then we need to ignore it. It could be + // the result of a two-way binding to SelectedIndex writing back to the + // property. The binding system should really be fixed to ensure that it's not + // writing back the same value, but this is a workaround until the binding + // refactor is complete. See #13676. + if (value == _selectedIndex) + return; + } + using var update = BatchUpdate(); Clear(); Select(value); @@ -675,8 +687,6 @@ namespace Avalonia.Controls.Selection } } - - if (raisePropertyChanged) { if (oldSelectedIndex != _selectedIndex) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs index 3a74ee6a7f..1863681030 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs @@ -3,6 +3,7 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Styling; @@ -577,6 +578,64 @@ namespace Avalonia.Controls.UnitTests Assert.True(target.ContainerFromIndex(3).IsFocused); } + [Fact] + public void SelectAll_Works_From_No_Selection_When_SelectedItem_Is_Bound_TwoWay() + { + // Issue #13676 + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + Width = 100, + Height = 100, + }; + + var root = new TestRoot(target); + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.Bind(ListBox.SelectedItemProperty, new Binding("Tag") + { + Mode = BindingMode.TwoWay, + RelativeSource = new RelativeSource(RelativeSourceMode.Self), + }); + + target.SelectAll(); + + Assert.Equal(new[] { 0, 1, 2, 3 }, target.Selection.SelectedIndexes); + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Qux" }, target.SelectedItems); + } + + [Fact] + public void SelectAll_Works_From_No_Selection_When_SelectedIndex_Is_Bound_TwoWay() + { + // Issue #13676 + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + Width = 100, + Height = 100, + }; + + var root = new TestRoot(target); + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.Bind(ListBox.SelectedIndexProperty, new Binding("Tag") + { + Mode = BindingMode.TwoWay, + RelativeSource = new RelativeSource(RelativeSourceMode.Self), + }); + + target.SelectAll(); + + Assert.Equal(new[] { 0, 1, 2, 3 }, target.Selection.SelectedIndexes); + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Qux" }, target.SelectedItems); + } + private Control CreateListBoxTemplate(TemplatedControl parent, INameScope scope) { return new ScrollViewer