microsoft / microsoft/microsoft-ui-xaml
Bug Report: ItemsRepeater.TryGetElement() returns null for oldIndex after ObservableCollection.Move
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
When an `ItemsRepeater` is bound to an `ObservableCollection` and an item within the `ObservableCollection` are moved with `ObservableCollection.Move(oldIndex, newIndex)`, then `ItemsRepeater.TryGetElement(int index)` returns `null` for `index = oldIndex`.
### Why is this important?
Resolving this bug is important because it affects both correctness and trust in the UI layer.
If `ItemsRepeater` returns the wrong element (or fails to return one) after a move operation, features that depend on item lookup can behave unpredictably—selection, focus, keyboard navigation, automation, and event routing may target the wrong item. That can produce user-visible glitches, intermittent failures, and hard-to-reproduce regressions.
Fixing it provides three concrete benefits:
- Reliability: item-to-UI mapping stays correct after collection changes.
- Stability: reduces downstream null/invalid-state errors in app code.
- Maintainability: prevents fragile workarounds and lowers long-term support cost.
In short, this bug is not only a functional defect; it creates risk across interaction, accessibility, and developer confidence.
### Steps to reproduce the bug
### Reproduction steps:
1. Build and run the project available [here](https://github.com/engltom/ItemsRepeater_Bug.git)
2. Initially, the App shows as follows:
The two boxes displaying "Item 0" and "Item 1" are `ItemsRepeater`s that use two separate `ObservableCollection\`
3. Click "Swap items 0 and 1". This executes `ObservableCollection\.Move(0, 1);` for the `ItemsSource` of the upper `ItemsRepeater` and `ObservableCollection\.RemoveAt(0); ObservableCollection\.Insert(1, txt);` for the `ItemsSource of the lower `ItemsRepeater`.
4. After that for the upper `ItemsRepeater`, calling `ItemsRepeater.TryGetElement(0)` returns `null`, which results in the app displaying
5. Clicking on "Force update" Causes the texts on the right hand side to be updated. During the update, `ItemsRepeater.TryGetElement(0)` is called to generate the text "Text at index 0: ...". In case `ItemsRepeater.TryGetElement(0)` does not return a `TextBlock`, the text ends with *\{null\}*. Otherwise it displays the text of the `TextBlock` returned by `ItemsRepeater.TryGetElement(0)`. The "Text at index1: ..." is likewise created by calling `ItemsRepeater.TryGetElement(1)`. The update does not result in a different result.
### Relevant Code Locations
ItemsRepeaterBug/MainWindow.xaml
MoveTestRepeater
RemoveAndAddTestRepeater
Buttons: Swap items 0 and 1, Force update
ItemsRepeaterBug/MainWindow.xaml.cs
SetMoveInfo() uses MoveTestRepeater.TryGetElement(0/1)
Swap_Click() calls TestStringMove.Move(0, 1)
### Actual behavior
`ItemsRepeater.TryGetElement()` does not return the correct UIElement after NotifyCollectionChanged with Move.
### Expected behavior
`ItemsRepeater.TryGetElement()` returns the correct UIElement after NotifyCollectionChanged with Move.
### Screenshots
### NuGet package version
Microsoft.WindowsAppSDK 2.4.0, Microsoft.Windows.SDK.BuildTools 10.0.29648.1000-preview
### Windows version
_No response_
### Additional context
App type: WinUI desktop app
Target framework: net10.0-windows10.0.22000.0 (.NET 10)
IDE: Visual Studio Professional 2026 (18.9.1)
Key packages:
Microsoft.WindowsAppSDK 2.4.0
Microsoft.Windows.SDK.BuildTools 10.0.29648.1000-preview
Contributor guide
Research direction
Start by running the linked reproduction and inspect ItemsRepeaterBug/MainWindow.xaml and MainWindow.xaml.cs. Exercise the Swap items 0 and 1 and Force update buttons, comparing the Move path with the remove-and-insert path and the TryGetElement(0/1) results. Done means TryGetElement returns the correct UIElement after an ObservableCollection move.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, csharp
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100