diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 2fa4a02fa2..ffdd32f95c 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -179,6 +179,26 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Collapse the specified all descendent s.
+ ///
+ /// The item to collapse.
+ public void CollapseSubTree(TreeViewItem item)
+ {
+ item.IsExpanded = false;
+
+ if (item.Presenter?.Panel != null)
+ {
+ foreach (var child in item.Presenter.Panel.Children)
+ {
+ if (child is TreeViewItem treeViewItem)
+ {
+ CollapseSubTree(treeViewItem);
+ }
+ }
+ }
+ }
+
///
/// Selects all items in the .
///
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 9bfcf5adfa..18245bd682 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
@@ -166,30 +168,94 @@ namespace Avalonia.Controls
{
if (!e.Handled)
{
- switch (e.Key)
+ Func? handler =
+ e.Key switch
+ {
+ Key.Left => ApplyToItemOrRecursivelyIfCtrl(FocusAwareCollapseItem, e.KeyModifiers),
+ Key.Right => ApplyToItemOrRecursivelyIfCtrl(ExpandItem, e.KeyModifiers),
+ Key.Enter or Key.Space => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers),
+
+ // do not handle CTRL with numpad keys
+ Key.Subtract => FocusAwareCollapseItem,
+ Key.Add => ExpandItem,
+ Key.Divide => ApplyToSubtree(CollapseItem),
+ Key.Multiply => ApplyToSubtree(ExpandItem),
+ _ => null,
+ };
+
+ if (handler is not null)
+ {
+ e.Handled = handler(this);
+ }
+
+ // NOTE: these local functions do not use the TreeView.Expand/CollapseSubtree
+ // function because we want to know if any items were in fact expanded to set the
+ // event handled status. Also the handling here avoids a potential infinite recursion/stack overflow.
+ static Func ApplyToSubtree(Func f)
+ {
+ // Calling toList enumerates all items before applying functions. This avoids a
+ // potential infinite loop if there is an infinite tree (the control catalog is
+ // lazily infinite). But also means a lazily loaded tree will not be expanded completely.
+ return t => SubTree(t)
+ .ToList()
+ .Select(treeViewItem => f(treeViewItem))
+ .Aggregate(false, (p, c) => p || c);
+ }
+
+ static Func ApplyToItemOrRecursivelyIfCtrl(Func f, KeyModifiers keyModifiers)
+ {
+ if (keyModifiers.HasAllFlags(KeyModifiers.Control))
+ {
+ return ApplyToSubtree(f);
+ }
+
+ return f;
+ }
+
+ static bool ExpandItem(TreeViewItem treeViewItem)
+ {
+ if (treeViewItem.ItemCount > 0 && !treeViewItem.IsExpanded)
+ {
+ treeViewItem.IsExpanded = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool CollapseItem(TreeViewItem treeViewItem)
{
- case Key.Right:
- if (Items != null && Items.Cast