Browse Source

Fix focus issues with combo box #19396 (#19672)

* Fix focus issues with combo box #19396

* add unit tests for combobox focus issue
pull/19709/head
Julian 4 months ago
committed by GitHub
parent
commit
c23be27756
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      src/Avalonia.Controls/ComboBox.cs
  2. 133
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

9
src/Avalonia.Controls/ComboBox.cs

@ -276,6 +276,10 @@ namespace Avalonia.Controls
SetCurrentValue(IsDropDownOpenProperty, false);
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Tab)
{
SetCurrentValue(IsDropDownOpenProperty, false);
}
// Ignore key buttons, if they are used for XY focus.
else if (!IsDropDownOpen
&& !XYFocusHelpers.IsAllowedXYNavigationMode(this, e.KeyDeviceType))
@ -717,6 +721,11 @@ namespace Avalonia.Controls
{
_skipNextTextChanged = false;
}
//when changing the SelectedIndex it will call: KeyboardNavigation.SetTabOnceActiveElement(this, [combo box item]);
//this will then break tab navigation back into this combobox as it will try to focus the combo box item
//rather than the combobox or editable text box, so we need to SetTabOnceActiveElement to null
KeyboardNavigation.SetTabOnceActiveElement(this, null);
}
private string GetItemTextValue(object? item)

133
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -193,10 +193,6 @@ namespace Avalonia.Controls.UnitTests
{
[!ContentControl.ContentProperty] = parent[!ComboBox.SelectionBoxItemProperty],
},
new ToggleButton
{
Name = "toggle",
}.RegisterInNameScope(scope),
new Popup
{
Name = "PART_Popup",
@ -212,7 +208,7 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope),
new TextBox
{
Name = "PART_InputText"
Name = "PART_EditableTextBox"
}.RegisterInNameScope(scope)
}
};
@ -715,5 +711,132 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target.SelectedItem, items2[0]);
Assert.Equal(target.Text, "Value 1");
}
private void RaiseTabKeyPress(Control target, bool withShift = false)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = Key.Tab,
KeyModifiers = withShift ? KeyModifiers.Shift : KeyModifiers.None
});
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyUpEvent,
Key = Key.Tab,
KeyModifiers = withShift ? KeyModifiers.Shift : KeyModifiers.None
});
}
[Fact]
public void When_Tabbing_Out_With_Dropdown_Open_It_Closes()
{
using var app = UnitTestApplication.Start(TestServices.RealFocus);
var target = new ComboBox
{
ItemsSource = new[] { "Foo", "Bar" }
};
var nextControl = new ComboBox
{
ItemsSource = new[] { "Baz" }
};
var container = new StackPanel
{
Children =
{
target,
nextControl
}
};
var root = new TestRoot(container);
var keyboardNavHandler = new KeyboardNavigationHandler();
keyboardNavHandler.SetOwner(root);
target.Focus();
_helper.Down(target);
_helper.Up(target);
Assert.True(target.IsFocused);
Assert.True(target.IsDropDownOpen);
RaiseTabKeyPress(target);
Assert.False(target.IsFocused);
Assert.True(nextControl.IsFocused);
Assert.False(target.IsDropDownOpen);
}
[Fact]
public void When_Editable_And_Item_Selected_Via_Text_Then_Focus_Swaps_Via_Tab_Swapping_Back_Should_Focus_TextBox()
{
using var app = UnitTestApplication.Start(TestServices.RealFocus);
var items = new[]
{
new Item("Value 1", "Display 1"),
new Item("Value 2", "Display 2")
};
var target = new ComboBox
{
DisplayMemberBinding = new Binding("Display"),
IsEditable = true,
IsTabStop = false,
ItemsSource = items,
Template = GetTemplate()
};
TextSearch.SetTextBinding(target, new Binding("Value"));
KeyboardNavigation.SetTabNavigation(target, KeyboardNavigationMode.Local);
var previousControl = new ComboBox
{
ItemsSource = new[] { "Baz" }
};
var container = new StackPanel
{
Children =
{
previousControl,
target
}
};
var root = new TestRoot(container);
var keyboardNavHandler = new KeyboardNavigationHandler();
keyboardNavHandler.SetOwner(root);
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var containerPanel = target.GetTemplateChildren().OfType<Panel>().FirstOrDefault(x => x.Name == "container");
var editableTextBox = containerPanel?.GetVisualDescendants().OfType<TextBox>().FirstOrDefault(x => x.Name == "PART_EditableTextBox");
var popup = containerPanel?.GetVisualDescendants().OfType<Popup>().FirstOrDefault(x => x.Name == "PART_Popup");
var popupScrollViewer = popup?.Child as ScrollViewer;
var scrollViewerItemsPresenter = popupScrollViewer?.Content as ItemsPresenter;
var popupVirtualizingStackPanel = scrollViewerItemsPresenter?.GetVisualDescendants().OfType<VirtualizingStackPanel>().FirstOrDefault();
Assert.NotNull(popupVirtualizingStackPanel);
//force the popup to render the ComboBoxItem(s) as they are what get set as "focused" if this test fails
popupVirtualizingStackPanel.Measure(Size.Infinity);
target.Focus();
Assert.True(editableTextBox.IsFocused);
target.Text = "Value 1";
Assert.Same(target.SelectedItem, items[0]);
var item1 = scrollViewerItemsPresenter.ContainerFromIndex(0);
Assert.IsType<ComboBoxItem>(item1);
RaiseTabKeyPress(target, withShift: true);
Assert.False(target.IsFocused);
Assert.True(previousControl.IsFocused);
RaiseTabKeyPress(previousControl);
var focused = root.FocusManager.GetFocusedElement();
Assert.Same(editableTextBox, focused);
}
}
}

Loading…
Cancel
Save