Skip to content

Commit 50e87cb

Browse files
Fix: Picker selects incorrect item after item removal (#29313)
* Fixed the selected item changed * fix for adding items * Added the test cases * Modified the logics * Modified the test case * Corrected the code * Modified the logics * Modified the changes * Add unit tests for Picker selection preservation - Add PickerPreservesSelectedItemAfterRemovingItemBeforeSelection test - Add PickerPreservesSelectedItemAfterInsertingItemBeforeSelection test - Update existing tests to verify the new correct behavior * Remove UI test, keep only unit test Unit tests are sufficient to validate this fix. --------- Co-authored-by: Stephane Delcroix <[email protected]>
1 parent f7ffadf commit 50e87cb

File tree

2 files changed

+99
-20
lines changed

2 files changed

+99
-20
lines changed

src/Controls/src/Core/Picker/Picker.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
275275
if (((LockableObservableListWrapper)Items).IsLocked)
276276
return;
277277

278-
ClampSelectedIndex();
278+
int index = GetSelectedIndex();
279+
ClampSelectedIndex(index);
279280
Handler?.UpdateValue(nameof(IPicker.Items));
280281
}
281282

@@ -370,8 +371,13 @@ void AddItems(NotifyCollectionChangedEventArgs e)
370371
int index = insertIndex;
371372
foreach (object newItem in e.NewItems)
372373
((LockableObservableListWrapper)Items).InternalInsert(index++, GetDisplayMember(newItem));
373-
if (insertIndex <= SelectedIndex)
374-
UpdateSelectedItem(SelectedIndex);
374+
375+
index = GetSelectedIndex();
376+
if (insertIndex <= index)
377+
{
378+
// When an item is inserted before the current selection, the selected item changes because the selected index is not properly updated.
379+
ClampSelectedIndex(index);
380+
}
375381
}
376382

377383
void RemoveItems(NotifyCollectionChangedEventArgs e)
@@ -395,10 +401,23 @@ void RemoveItems(NotifyCollectionChangedEventArgs e)
395401

396402
foreach (object _ in e.OldItems)
397403
((LockableObservableListWrapper)Items).InternalRemoveAt(index--);
398-
if (removeStart <= SelectedIndex)
404+
405+
index = GetSelectedIndex();
406+
if (removeStart <= index)
407+
{
408+
ClampSelectedIndex(index);
409+
}
410+
}
411+
412+
int GetSelectedIndex()
413+
{
414+
if (SelectedItem is null)
399415
{
400-
ClampSelectedIndex();
416+
return SelectedIndex;
401417
}
418+
419+
int newIndex = ItemsSource?.IndexOf(SelectedItem) ?? Items?.IndexOf(SelectedItem) ?? -1;
420+
return newIndex >= 0 ? newIndex : SelectedIndex;
402421
}
403422

404423
void ResetItems()
@@ -410,7 +429,7 @@ void ResetItems()
410429
((LockableObservableListWrapper)Items).InternalAdd(GetDisplayMember(item));
411430
Handler?.UpdateValue(nameof(IPicker.Items));
412431

413-
ClampSelectedIndex();
432+
ClampSelectedIndex(SelectedIndex);
414433
}
415434

416435
static void OnSelectedIndexChanged(object bindable, object oldValue, object newValue)
@@ -426,10 +445,10 @@ static void OnSelectedItemChanged(BindableObject bindable, object oldValue, obje
426445
picker.UpdateSelectedIndex(newValue);
427446
}
428447

429-
void ClampSelectedIndex()
448+
void ClampSelectedIndex(int selectedIndex)
430449
{
431-
var oldIndex = SelectedIndex;
432-
var newIndex = SelectedIndex.Clamp(-1, Items.Count - 1);
450+
var oldIndex = selectedIndex;
451+
var newIndex = selectedIndex.Clamp(-1, Items.Count - 1);
433452
//FIXME use the specificity of the caller
434453
SetValue(SelectedIndexProperty, newIndex, SetterSpecificity.FromHandler);
435454
// If the index has not changed, still need to change the selected item

src/Controls/tests/Core.UnitTests/PickerTests.cs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -401,20 +401,24 @@ public void TestItemsSourceCollectionChangedInsertBeforeSelected(int insertionIn
401401
ItemsSource = items,
402402
SelectedIndex = 1
403403
};
404+
var originalSelectedItem = picker.SelectedItem;
404405
items.InsertRange(insertionIndex, insertNames.Select(name => new { Name = name }));
405406
Assert.Equal(3 + insertNames.Length, picker.Items.Count);
406-
Assert.Equal(1, picker.SelectedIndex);
407-
Assert.Equal(items[1], picker.SelectedItem);
407+
// The selected item should remain the same, but the index should update
408+
Assert.Equal(originalSelectedItem, picker.SelectedItem);
409+
Assert.Equal(items.IndexOf(originalSelectedItem), picker.SelectedIndex);
408410
}
409411

410412
[Theory]
411-
[InlineData(0, 1)]
412-
[InlineData(1, 1)]
413-
[InlineData(2, 1)]
414-
[InlineData(0, 2)]
415-
[InlineData(1, 2)]
416-
[InlineData(2, 2)]
417-
public void TestItemsSourceCollectionChangedRemoveBeforeSelected(int removeIndex, int removeCount)
413+
// Cases where removed items do NOT include the selected item (Paul at index 1)
414+
[InlineData(0, 1, true)] // Remove John - Paul should be preserved
415+
[InlineData(2, 1, true)] // Remove Ringo - Paul should be preserved
416+
[InlineData(2, 2, true)] // Remove Ringo and George - Paul should be preserved
417+
// Cases where removed items include the selected item
418+
[InlineData(1, 1, false)] // Remove Paul - selection changes
419+
[InlineData(0, 2, false)] // Remove John and Paul - selection changes
420+
[InlineData(1, 2, false)] // Remove Paul and Ringo - selection changes
421+
public void TestItemsSourceCollectionChangedRemoveBeforeSelected(int removeIndex, int removeCount, bool selectedItemPreserved)
418422
{
419423
var items = new ObservableRangeCollection<object>
420424
{
@@ -429,11 +433,21 @@ public void TestItemsSourceCollectionChangedRemoveBeforeSelected(int removeIndex
429433
ItemsSource = items,
430434
SelectedIndex = 1
431435
};
436+
var originalSelectedItem = picker.SelectedItem;
432437
items.RemoveRange(removeIndex, removeCount);
433438

434439
Assert.Equal(4 - removeCount, picker.Items.Count);
435-
Assert.Equal(1, picker.SelectedIndex);
436-
Assert.Equal(items[1], picker.SelectedItem);
440+
if (selectedItemPreserved)
441+
{
442+
// The selected item should remain the same, but the index should update
443+
Assert.Equal(originalSelectedItem, picker.SelectedItem);
444+
Assert.Equal(items.IndexOf(originalSelectedItem), picker.SelectedIndex);
445+
}
446+
else
447+
{
448+
// The selected item was removed, so a different item is now selected
449+
Assert.NotEqual(originalSelectedItem, picker.SelectedItem);
450+
}
437451
}
438452

439453
[Theory]
@@ -792,5 +806,51 @@ public void NullItemReturnsEmptyStringFromInterface()
792806
var thing = (picker as IPicker).GetItem(0);
793807
Assert.NotNull(thing);
794808
}
809+
810+
// https://github.com/dotnet/maui/issues/29235
811+
[Fact]
812+
public void PickerPreservesSelectedItemAfterRemovingItemBeforeSelection()
813+
{
814+
// Arrange: Create a picker with items and select "Item2" (index 2)
815+
var items = new ObservableCollection<string> { "Item0", "Item1", "Item2" };
816+
var picker = new Picker
817+
{
818+
ItemsSource = items,
819+
SelectedIndex = 2 // Select "Item2"
820+
};
821+
822+
Assert.Equal("Item2", picker.SelectedItem);
823+
Assert.Equal(2, picker.SelectedIndex);
824+
825+
// Act: Remove the first item (before the selection)
826+
items.RemoveAt(0);
827+
828+
// Assert: SelectedItem should still be "Item2", but index should now be 1
829+
Assert.Equal("Item2", picker.SelectedItem);
830+
Assert.Equal(1, picker.SelectedIndex);
831+
}
832+
833+
// https://github.com/dotnet/maui/issues/29235
834+
[Fact]
835+
public void PickerPreservesSelectedItemAfterInsertingItemBeforeSelection()
836+
{
837+
// Arrange: Create a picker with items and select "Dog" (index 1)
838+
var items = new ObservableCollection<string> { "Cat", "Dog", "Rabbit" };
839+
var picker = new Picker
840+
{
841+
ItemsSource = items,
842+
SelectedIndex = 1 // Select "Dog"
843+
};
844+
845+
Assert.Equal("Dog", picker.SelectedItem);
846+
Assert.Equal(1, picker.SelectedIndex);
847+
848+
// Act: Insert an item at the beginning
849+
items.Insert(0, "Goat");
850+
851+
// Assert: SelectedItem should still be "Dog", and index should now be 2
852+
Assert.Equal("Dog", picker.SelectedItem);
853+
Assert.Equal(2, picker.SelectedIndex);
854+
}
795855
}
796856
}

0 commit comments

Comments
 (0)