Browse Source

Handle navigation in TreeView.

repro-ienumerable-menu-navigation
Steven Kirk 8 years ago
parent
commit
9e2e266d3c
  1. 72
      src/Avalonia.Controls/ItemsControl.cs
  2. 86
      src/Avalonia.Controls/TreeView.cs
  3. 2
      src/Avalonia.Controls/TreeViewItem.cs
  4. 70
      src/Avalonia.Input/NavigationDirection.cs

72
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);
}
/// <summary>
/// Handles directional navigation within the <see cref="ItemsControl"/>.
/// </summary>
/// <param name="e">The key events.</param>
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<IInputElement>()
.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();
}
}
}
}

86
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;
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{

2
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.
}
}
}

70
src/Avalonia.Input/NavigationDirection.cs

@ -58,4 +58,74 @@ namespace Avalonia.Input
/// </summary>
PageDown,
}
public static class NavigationDirectionExtensions
{
/// <summary>
/// Checks whether a <see cref="NavigationDirection"/> represents a tab movement.
/// </summary>
/// <param name="direction">The direction.</param>
/// <returns>
/// True if the direction represents a tab movement (<see cref="NavigationDirection.Next"/>
/// or <see cref="NavigationDirection.Previous"/>); otherwise false.
/// </returns>
public static bool IsTab(this NavigationDirection direction)
{
return direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous;
}
/// <summary>
/// Checks whether a <see cref="NavigationDirection"/> represents a directional movement.
/// </summary>
/// <param name="direction">The direction.</param>
/// <returns>
/// True if the direction represents a directional movement (any value except
/// <see cref="NavigationDirection.Next"/> and <see cref="NavigationDirection.Previous"/>);
/// otherwise false.
/// </returns>
public static bool IsDirectional(this NavigationDirection direction)
{
return direction > NavigationDirection.Previous ||
direction <= NavigationDirection.PageDown;
}
/// <summary>
/// Converts a keypress into a <see cref="NavigationDirection"/>.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="modifiers">The keyboard modifiers.</param>
/// <returns>
/// A <see cref="NavigationDirection"/> if the keypress represents a navigation keypress.
/// </returns>
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;
}
}
}
}

Loading…
Cancel
Save