diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 998111e8b9..e2d2f0a516 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -15,6 +15,7 @@ using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -323,35 +324,37 @@ namespace Avalonia.Controls
LogicalChildren.RemoveAll(toRemove);
}
+ ///
+ /// Handles directional navigation within the .
+ ///
+ /// The key events.
protected override void OnKeyDown(KeyEventArgs e)
{
- if (Presenter?.Panel is INavigableContainer container)
+ var focus = FocusManager.Instance;
+ var direction = e.Key.ToNavigationDirection();
+ var container = Presenter?.Panel as INavigableContainer;
+
+ if (container == null ||
+ focus.Current == null ||
+ direction == null ||
+ direction.Value.IsTab())
{
- var focus = FocusManager.Instance;
- var current = focus.Current;
- NavigationDirection? direction = null;
+ return;
+ }
- switch (e.Key)
- {
- case Key.Up: direction = NavigationDirection.Up; break;
- case Key.Down: direction = NavigationDirection.Down; break;
- case Key.Left: direction = NavigationDirection.Left; break;
- case Key.Right: direction = NavigationDirection.Right; break;
- case Key.Home: direction = NavigationDirection.First; break;
- case Key.End: direction = NavigationDirection.Last; break;
- case Key.PageUp: direction = NavigationDirection.PageUp; break;
- case Key.PageDown: direction = NavigationDirection.PageDown; break;
- }
+ var current = focus.Current
+ .GetSelfAndVisualAncestors()
+ .OfType()
+ .FirstOrDefault(x => x.VisualParent == container);
- if (direction != null && current != null)
- {
- var next = container.GetControl(direction.Value, current);
+ if (current != null)
+ {
+ var next = container.GetControl(direction.Value, current);
- if (next != null)
- {
- focus.Focus(next, NavigationMethod.Directional);
- e.Handled = true;
- }
+ if (next != null)
+ {
+ focus.Focus(next, NavigationMethod.Directional);
+ e.Handled = true;
}
}
@@ -370,6 +373,7 @@ namespace Avalonia.Controls
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
+ UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue);
SubscribeToItems(newValue);
@@ -393,10 +397,8 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
-
- int? count = (Items as IList)?.Count;
- if (count != null)
- ItemCount = (int)count;
+
+ UpdateItemCount();
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
@@ -480,5 +482,21 @@ namespace Avalonia.Controls
// TODO: Rebuild the item containers.
}
}
+
+ private void UpdateItemCount()
+ {
+ if (Items == null)
+ {
+ ItemCount = 0;
+ }
+ else if (Items is IList list)
+ {
+ ItemCount = list.Count;
+ }
+ else
+ {
+ ItemCount = Items.Count();
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 2e1c011685..4575fa767b 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -136,6 +136,92 @@ namespace Avalonia.Controls
}
}
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ var direction = e.Key.ToNavigationDirection();
+
+ if (direction?.IsDirectional() == true && !e.Handled)
+ {
+ if (SelectedItem != null)
+ {
+ var next = GetContainerInDirection(
+ GetContainerFromEventSource(e.Source) as TreeViewItem,
+ direction.Value,
+ true);
+
+ if (next != null)
+ {
+ FocusManager.Instance.Focus(next, NavigationMethod.Directional);
+ e.Handled = true;
+ }
+ }
+ else
+ {
+ SelectedItem = ElementAt(Items, 0);
+ }
+ }
+ }
+
+ private TreeViewItem GetContainerInDirection(
+ TreeViewItem from,
+ NavigationDirection direction,
+ bool intoChildren)
+ {
+ IItemContainerGenerator parentGenerator;
+
+ if (from?.Parent is TreeView treeView)
+ {
+ parentGenerator = treeView.ItemContainerGenerator;
+ }
+ else if (from?.Parent is TreeViewItem item)
+ {
+ parentGenerator = item.ItemContainerGenerator;
+ }
+ else
+ {
+ return null;
+ }
+
+ var index = parentGenerator.IndexFromContainer(from);
+ var parent = from.Parent as ItemsControl;
+ TreeViewItem result = null;
+
+ switch (direction)
+ {
+ case NavigationDirection.Up:
+ if (index > 0)
+ {
+ var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1);
+ result = previous.IsExpanded ?
+ (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) :
+ previous;
+ }
+ else
+ {
+ result = from.Parent as TreeViewItem;
+ }
+
+ break;
+
+ case NavigationDirection.Down:
+ if (from.IsExpanded && intoChildren)
+ {
+ result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0);
+ }
+ else if (index < parent?.ItemCount - 1)
+ {
+ result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1);
+ }
+ else if (parent is TreeViewItem parentItem)
+ {
+ return GetContainerInDirection(parentItem, direction, false);
+ }
+ break;
+ }
+
+ return result;
+ }
+
///
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 8af3333dd4..0886c05038 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -124,7 +124,7 @@ namespace Avalonia.Controls
}
}
- base.OnKeyDown(e);
+ // Don't call base.OnKeyDown - let events bubble up to containing TreeView.
}
}
}
diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs
index fbaa7e74c7..406890b767 100644
--- a/src/Avalonia.Input/NavigationDirection.cs
+++ b/src/Avalonia.Input/NavigationDirection.cs
@@ -58,4 +58,74 @@ namespace Avalonia.Input
///
PageDown,
}
+
+ public static class NavigationDirectionExtensions
+ {
+ ///
+ /// Checks whether a represents a tab movement.
+ ///
+ /// The direction.
+ ///
+ /// True if the direction represents a tab movement (
+ /// or ); otherwise false.
+ ///
+ public static bool IsTab(this NavigationDirection direction)
+ {
+ return direction == NavigationDirection.Next ||
+ direction == NavigationDirection.Previous;
+ }
+
+ ///
+ /// Checks whether a represents a directional movement.
+ ///
+ /// The direction.
+ ///
+ /// True if the direction represents a directional movement (any value except
+ /// and );
+ /// otherwise false.
+ ///
+ public static bool IsDirectional(this NavigationDirection direction)
+ {
+ return direction > NavigationDirection.Previous ||
+ direction <= NavigationDirection.PageDown;
+ }
+
+ ///
+ /// Converts a keypress into a .
+ ///
+ /// The key.
+ /// The keyboard modifiers.
+ ///
+ /// A if the keypress represents a navigation keypress.
+ ///
+ public static NavigationDirection? ToNavigationDirection(
+ this Key key,
+ InputModifiers modifiers = InputModifiers.None)
+ {
+ switch (key)
+ {
+ case Key.Tab:
+ return (modifiers & InputModifiers.Shift) != 0 ?
+ NavigationDirection.Next : NavigationDirection.Previous;
+ case Key.Up:
+ return NavigationDirection.Up;
+ case Key.Down:
+ return NavigationDirection.Down;
+ case Key.Left:
+ return NavigationDirection.Left;
+ case Key.Right:
+ return NavigationDirection.Right;
+ case Key.Home:
+ return NavigationDirection.First;
+ case Key.End:
+ return NavigationDirection.Last;
+ case Key.PageUp:
+ return NavigationDirection.PageUp;
+ case Key.PageDown:
+ return NavigationDirection.PageDown;
+ default:
+ return null;
+ }
+ }
+ }
}