Browse Source

Fix #4030: Enter did not activate the selected tree row

Avalonia 12 treats plain Enter/Space on a ListBoxItem as selection
input: the container marks the KeyDown handled before it bubbles, so
SharpTreeView.OnKeyDown never saw the keys and its activation handling
(navigate to the member from an analyzer row, toggle a checkable row)
was dead. Override ShouldTriggerSelection -- the extension point added
for this in Avalonia 12 -- to suppress the selection trigger exactly
for the case OnKeyDown activates instead: a single selected row that
is the row the key landed on. Multi-row selections keep the default
collapse-to-focused-row behaviour.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/4039/head
Christoph Wille 4 weeks ago
parent
commit
a04d40b895
  1. 33
      ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs
  2. 21
      ILSpy/Controls/TreeView/SharpTreeView.cs

33
ILSpy.Tests/Analyzers/AnalyzerTreeKeyboardTests.cs

@ -70,6 +70,39 @@ public class AnalyzerTreeKeyboardTests @@ -70,6 +70,39 @@ public class AnalyzerTreeKeyboardTests
description: "Right must expand the node via SharpTreeView.OnKeyDown on the analyzer tree");
}
[AvaloniaTest]
public async Task Enter_Activates_The_Selected_Analyzer_Node()
{
// Enter on a single selected analyzer row activates it -- for an entity node that means
// navigating to the member's home in the assembly tree, like 10.x did. The key must reach
// SharpTreeView.OnKeyDown: the container is a ListBoxItem, and Avalonia's default key
// selection triggers treat Enter/Space as selection input and mark the event handled
// before it bubbles, so SharpTreeView suppresses that trigger for the activation case.
var (window, vm) = await TestHarness.BootAsync(3);
var dockWorkspace = AppComposition.Current.GetExport<DockWorkspace>();
var analyzerVm = AppComposition.Current.GetExport<AnalyzerTreeViewModel>();
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"System.Linq", "System.Linq", "System.Linq.Enumerable");
var entity = (ITypeDefinition)typeNode.Member!;
var analyzed = analyzerVm.Analyze(entity);
dockWorkspace.ShowToolPane(AnalyzerTreeViewModel.PaneContentId);
var view = await window.WaitForComponent<ICSharpCode.ILSpy.Analyzers.AnalyzerTreeView>();
var tree = await view.WaitForComponent<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeView>();
tree.SelectedItem = analyzed;
Dispatcher.UIThread.RunJobs();
tree.FocusNode(analyzed);
Dispatcher.UIThread.RunJobs();
((object?)vm.AssemblyTreeModel.SelectedItem).Should().NotBeSameAs(typeNode,
"precondition: the assembly tree must not already sit on the target node");
window.KeyPress(Key.Enter, RawInputModifiers.None, PhysicalKey.Enter, null);
await Waiters.WaitForAsync(() => ReferenceEquals(vm.AssemblyTreeModel.SelectedItem, typeNode),
description: "Enter must activate the analyzer node and select the type in the assembly tree");
}
[AvaloniaTest]
public async Task Ctrl_R_Analyzes_The_Selected_Member()
{

21
ILSpy/Controls/TreeView/SharpTreeView.cs

@ -295,6 +295,27 @@ namespace ICSharpCode.ILSpy.Controls.TreeView @@ -295,6 +295,27 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
scrollViewer.Offset = new Vector(scrollViewer.Offset.X, newOffsetY);
}
/// <summary>
/// Avalonia's default key selection triggers treat plain Enter/Space as selection input:
/// the ListBoxItem container marks the KeyDown handled before it bubbles here, so the
/// activation handling in <see cref="OnKeyDown"/> would never see those keys. Suppress the
/// selection trigger exactly for the case OnKeyDown activates instead -- a single selected
/// row that is the row the key landed on. Multi-row selections keep the default behaviour
/// (Enter/Space collapses the selection to the focused row).
/// </summary>
protected override bool ShouldTriggerSelection(Visual selectable, KeyEventArgs eventArgs)
{
if (eventArgs.KeyModifiers == KeyModifiers.None
&& eventArgs.Key is Key.Enter or Key.Space
&& selectable is SharpTreeViewItem { Node: { } node }
&& SelectedItems?.Count == 1
&& ReferenceEquals(SelectedItem, node))
{
return false;
}
return base.ShouldTriggerSelection(selectable, eventArgs);
}
protected override void OnKeyDown(KeyEventArgs e)
{
// Ctrl+A select-all must work on the first press even before a current item is

Loading…
Cancel
Save