diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
index decbd763a1..64e80a8e11 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
@@ -1,77 +1,95 @@
-
-
- A drop-down list.
+
+
+ A drop-down list.
-
-
-
-
-
- Inline Items
- Inline Item 2
- Inline Item 3
- Inline Item 4
-
+
+
+
+
+
-
-
-
-
- Hello
- World
-
-
-
-
-
-
-
-
-
-
-
+
+ Inline Items
+ Inline Item 2
+ Inline Item 3
+ Inline Item 4
+
-
-
-
-
- Control Items
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Hello
+ World
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Inline Items
- Inline Item 2
- Inline Item 3
- Inline Item 4
-
-
-
-
-
+
+
+
+
+ Control Items
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+ Inline Items
+ Inline Item 2
+ Inline Item 3
+ Inline Item 4
+
+
+
+
+
+
+ WrapSelection
+
+
+
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
index d50b051d9f..d304bf227d 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
@@ -2,6 +2,7 @@ using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@@ -10,6 +11,7 @@ namespace ControlCatalog.Pages
public ComboBoxPage()
{
this.InitializeComponent();
+ DataContext = new ComboBoxPageViewModel();
}
private void InitializeComponent()
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index 41658329df..433592345a 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -21,6 +21,7 @@
Toggle
AlwaysSelected
AutoScrollToSelectedItem
+ WrapSelection
@@ -30,6 +31,7 @@
+ SelectionMode="{Binding SelectionMode^}"
+ WrapSelection="{Binding WrapSelection}"/>
diff --git a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
new file mode 100644
index 0000000000..bbe970afd6
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using Avalonia.Controls;
+using Avalonia.Controls.Selection;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels
+{
+ public class ComboBoxPageViewModel : ViewModelBase
+ {
+ private bool _wrapSelection;
+
+ public bool WrapSelection
+ {
+ get => _wrapSelection;
+ set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
index 7f2d6e9572..59489ebcc0 100644
--- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
@@ -14,6 +14,7 @@ namespace ControlCatalog.ViewModels
private bool _toggle;
private bool _alwaysSelected;
private bool _autoScrollToSelectedItem = true;
+ private bool _wrapSelection;
private int _counter;
private IObservable _selectionMode;
@@ -85,6 +86,12 @@ namespace ControlCatalog.ViewModels
set => this.RaiseAndSetIfChanged(ref _autoScrollToSelectedItem, value);
}
+ public bool WrapSelection
+ {
+ get => _wrapSelection;
+ set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+ }
+
public MiniCommand AddItemCommand { get; }
public MiniCommand RemoveItemCommand { get; }
public MiniCommand SelectRandomItemCommand { get; }
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index e9eca97e13..72b09b7a3c 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -10,9 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
-using Avalonia.LogicalTree;
using Avalonia.Media;
-using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@@ -91,7 +89,7 @@ namespace Avalonia.Controls
{
ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
FocusableProperty.OverrideDefaultValue(true);
- SelectedItemProperty.Changed.AddClassHandler((x,e) => x.SelectedItemChanged(e));
+ SelectedItemProperty.Changed.AddClassHandler((x, e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
IsTextSearchEnabledProperty.OverrideDefaultValue(true);
}
@@ -221,8 +219,9 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
+ // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl.
else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
- (e.Key == Key.Up || e.Key == Key.Down))
+ (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true)
{
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null)
@@ -430,7 +429,18 @@ namespace Avalonia.Controls
int next = SelectedIndex + 1;
if (next >= ItemCount)
- next = 0;
+ {
+ if (WrapSelection == true)
+ {
+ next = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+
SelectedIndex = next;
}
@@ -440,7 +450,16 @@ namespace Avalonia.Controls
int prev = SelectedIndex - 1;
if (prev < 0)
- prev = ItemCount - 1;
+ {
+ if (WrapSelection == true)
+ {
+ prev = ItemCount - 1;
+ }
+ else
+ {
+ return;
+ }
+ }
SelectedIndex = prev;
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 10e12a1ae0..ed8f9efb2e 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -143,6 +143,8 @@ namespace Avalonia.Controls
protected set;
}
+ private protected bool WrapFocus { get; set; }
+
event EventHandler? IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
@@ -315,7 +317,7 @@ namespace Avalonia.Controls
{
if (current.VisualParent == container && current is IInputElement inputElement)
{
- var next = GetNextControl(container, direction.Value, inputElement, false);
+ var next = GetNextControl(container, direction.Value, inputElement, WrapFocus);
if (next != null)
{
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index a34e5d6438..361febf305 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -233,11 +233,6 @@ namespace Avalonia.Controls.Presenters
var itemIndex = generator.IndexFromContainer(from);
var vertical = VirtualizingPanel.ScrollDirection == Orientation.Vertical;
- if (itemIndex == -1)
- {
- return null;
- }
-
var newItemIndex = -1;
switch (direction)
@@ -250,6 +245,16 @@ namespace Avalonia.Controls.Presenters
newItemIndex = ItemCount - 1;
break;
+ default:
+ if (itemIndex == -1)
+ {
+ return null;
+ }
+ break;
+ }
+
+ switch (direction)
+ {
case NavigationDirection.Up:
if (vertical)
{
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 840a5ac1dc..b4cfd9404c 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -114,6 +114,12 @@ namespace Avalonia.Controls.Primitives
"SelectionChanged",
RoutingStrategies.Bubble);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty WrapSelectionProperty =
+ AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false);
+
private static readonly IList Empty = Array.Empty