AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.94k stars 2.24k forks source link

ListBox/VirtualizingStackPanel reverses order of containers when ItemsSource binding changes, breaking Tab navigation #11846

Open IanRawley opened 1 year ago

IanRawley commented 1 year ago

Describe the bug When a ListBox with VirtualizingStackPanel has its ItemsSource changed, the VirtualizingStackPanel recycles all the existing containers by pushing them onto a stack. The containers remain attached to the Visual Tree however, so when the new items are added to containers the first item gets added to the last container on the visual tree.

When you use Tab navigation to enter the list, the effect is that the ListBoxItem which receives focus swaps between the first visible item and last visible item every time the source changes.

To Reproduce Steps to reproduce the behavior:

  1. Clone and build repository https://github.com/IanRawley/ListboxTabNavigationBug.git
  2. Use Tab to navigate into the ListBox, note that the first item ("0") receives focus.
  3. Press the button, which generates a new list (with identical names).
  4. Tab into the ListBox again, and see that the last visible item receives focus.
  5. Press the button again, and Tab into the ListBox. Now the first item receives focus.

Expected behavior When Tab is used to navigate into a ListBox the first visible item (or alternatively currently selected item) always receives focus.

Desktop (please complete the following information):

Additional context A simple fix would be for the VirtualizingStackPanel to use something FIFO for it's recycle pools rather than FILO, but I'm not sure what other implications there might be. I suspect Tab navigation and VirtualizingStackPanels with heterogeneous container collections also has issues, but haven't investigated.

timunie commented 1 year ago

@IanRawley some virtualization fixes were made recently iirc. Can you test against latest nightly?

IanRawley commented 1 year ago

@timunie No change with nightly build 11.0.999-cibuild0036784-beta

timunie commented 1 year ago

thanks for testing anyway 👍

grokys commented 11 months ago

Taken a look at this and there are two solutions I can see:

  1. Use a queue instead of a stack for the recycle pool. This fixes the above issue, but when no item is selected and the list is scrolled, causes an arbitrary item to be focused when tabbing
  2. When realising items, set the TabIndex for the container to the index of the item. This fixes the above issue and also causes the first realised item to be focused when when no item is selected and the list is scrolled. This will also have the advantage of making tab navigation work properly when the tab navigation is set to a value other than Once on the items control.

2 looks like the best solution, but I can't see WPF doing this anywhere. Not sure if there are any unintended downsides. I'm currently on macOS and unable to test WPF's behavior, so leaving my findings here. Will test how WPF handles this when I'm at a Windows machine.

grokys commented 11 months ago

Actually, looking at WPF, it seems that the VirtualizingStackPanel modifies its visual children collection to match the order of realized elements. Our visual children collection works a little differently to WPF, but this gives us a 3rd alternative:

  1. Provide an overridable sorted "view" of visual children that TabNavigation.GetNextSibling can use