@ -59,7 +59,6 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
AvaloniaProperty . Register < SharpTreeView , bool > ( nameof ( ShowLines ) , defaultValue : true ) ;
AvaloniaProperty . Register < SharpTreeView , bool > ( nameof ( ShowLines ) , defaultValue : true ) ;
TreeFlattener ? flattener ;
TreeFlattener ? flattener ;
bool doNotScrollOnExpanding ;
string searchBuffer = string . Empty ;
string searchBuffer = string . Empty ;
DispatcherTimer ? searchResetTimer ;
DispatcherTimer ? searchResetTimer ;
@ -92,6 +91,12 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
AddHandler ( DragDrop . DragOverEvent , OnDragOver ) ;
AddHandler ( DragDrop . DragOverEvent , OnDragOver ) ;
AddHandler ( DragDrop . DropEvent , OnDrop ) ;
AddHandler ( DragDrop . DropEvent , OnDrop ) ;
AddHandler ( DragDrop . DragLeaveEvent , ( _ , _ ) = > HideInsertMarker ( ) ) ;
AddHandler ( DragDrop . DragLeaveEvent , ( _ , _ ) = > HideInsertMarker ( ) ) ;
// The row template's expander writes IsExpanded straight to the node, so an expansion
// made with the mouse never passes through this control; its Click is what identifies
// one. Only a gesture scrolls: code that expands nodes to reveal a selection (see
// ScrollIntoNodeView) or to open every match of a filter positions the viewport itself,
// and a scroll per expanded node would fight it.
AddHandler ( Button . ClickEvent , OnExpanderClick , RoutingStrategies . Bubble , handledEventsToo : true ) ;
}
}
public SharpTreeNode ? Root {
public SharpTreeNode ? Root {
@ -184,32 +189,47 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
node . ActivateItem ( args ) ;
node . ActivateItem ( args ) ;
if ( ! e . Handled & & node . ShowExpander )
if ( ! e . Handled & & node . ShowExpander )
{
{
node . IsExpanded = ! node . IsExpanded ;
SetExpanded ( node , ! node . IsExpanded ) ;
e . Handled = true ;
e . Handled = true ;
}
}
}
}
void OnExpanderClick ( object? sender , RoutedEventArgs e )
{
if ( e . Source is ToggleButton { Name : "PART_Expander" } expander
& & expander . DataContext is SharpTreeNode { IsExpanded : true } node )
{
HandleExpanding ( node ) ;
}
}
/// <summary>Expands or collapses <paramref name="node"/> as a user gesture, so an expansion
/// reveals its children the way <see cref="HandleExpanding"/> describes.</summary>
void SetExpanded ( SharpTreeNode node , bool expanded )
{
if ( node . IsExpanded = = expanded )
return ;
node . IsExpanded = expanded ;
if ( expanded )
HandleExpanding ( node ) ;
}
/// <summary>
/// <summary>
/// Called when a visible node expands so its newly shown children are scrolled into view
/// Scrolls the rows a just-expanded node revealed into view, the way the native Windows
/// (without scrolling the node itself off the top).
/// tree control does: far enough to show the new children, but never so far that the
/// expanded node itself leaves the viewport. Both steps only move the viewport when their
/// row lies outside it, so expanding a node whose children already fit below it does not
/// scroll at all.
/// </summary>
/// </summary>
internal void HandleExpanding ( SharpTreeNode node )
void HandleExpanding ( SharpTreeNode node )
{
{
if ( doNotScrollOnExpanding )
return ;
SharpTreeNode lastVisibleChild = node ;
SharpTreeNode lastVisibleChild = node ;
while ( true )
while ( lastVisibleChild . Children . LastOrDefault ( c = > c . IsVisible ) is { } child )
{
var child = lastVisibleChild . Children . LastOrDefault ( c = > c . IsVisible ) ;
if ( child = = null )
break ;
lastVisibleChild = child ;
lastVisibleChild = child ;
}
if ( lastVisibleChild = = node )
if ( lastVisibleChild ! = node )
return ;
{
ScrollRowIntoView ( lastVisibleChild , centre : false ) ;
ScrollRowIntoView ( lastVisibleChild , centre : false ) ;
Dispatcher . UIThread . Post ( ( ) = > ScrollRowIntoView ( node , centre : false ) , DispatcherPriority . Loaded ) ;
ScrollRowIntoView ( node , centre : false ) ;
}
}
}
/// <summary>Scrolls the node into view (unless <paramref name="scroll"/> is false) and gives it
/// <summary>Scrolls the node into view (unless <paramref name="scroll"/> is false) and gives it
@ -252,10 +272,8 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
public void ScrollIntoNodeView ( SharpTreeNode node )
public void ScrollIntoNodeView ( SharpTreeNode node )
{
{
ArgumentNullException . ThrowIfNull ( node ) ;
ArgumentNullException . ThrowIfNull ( node ) ;
doNotScrollOnExpanding = true ;
foreach ( var ancestor in node . Ancestors ( ) )
foreach ( var ancestor in node . Ancestors ( ) )
ancestor . IsExpanded = true ;
ancestor . IsExpanded = true ;
doNotScrollOnExpanding = false ;
CenterNodeInView ( node ) ;
CenterNodeInView ( node ) ;
}
}
@ -403,7 +421,7 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
{
{
case Key . Left :
case Key . Left :
if ( node . IsExpanded )
if ( node . IsExpanded )
node . IsExpanded = false ;
SetExpanded ( node , false ) ;
else if ( node . Parent ! = null & & ! node . Parent . IsRoot )
else if ( node . Parent ! = null & & ! node . Parent . IsRoot )
SelectAndFocus ( node . Parent ) ;
SelectAndFocus ( node . Parent ) ;
else
else
@ -412,7 +430,7 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
break ;
break ;
case Key . Right :
case Key . Right :
if ( ! node . IsExpanded & & node . ShowExpander )
if ( ! node . IsExpanded & & node . ShowExpander )
node . IsExpanded = true ;
SetExpanded ( node , true ) ;
else if ( node . Children . Count > 0 )
else if ( node . Children . Count > 0 )
SelectAndFocus ( node . Children . First ( c = > c . IsVisible ) ) ;
SelectAndFocus ( node . Children . First ( c = > c . IsVisible ) ) ;
else
else
@ -420,16 +438,18 @@ namespace ICSharpCode.ILSpy.Controls.TreeView
e . Handled = true ;
e . Handled = true ;
break ;
break ;
case Key . Add :
case Key . Add :
node . IsExpanded = true ;
SetExpanded ( node , true ) ;
e . Handled = true ;
e . Handled = true ;
break ;
break ;
case Key . Subtract :
case Key . Subtract :
node . IsExpanded = false ;
SetExpanded ( node , false ) ;
e . Handled = true ;
e . Handled = true ;
break ;
break ;
case Key . Multiply :
case Key . Multiply :
node . IsExpanded = true ;
node . IsExpanded = true ;
ExpandRecursively ( node ) ;
ExpandRecursively ( node ) ;
// The whole subtree is open now, so this reveals as much of it as fits.
HandleExpanding ( node ) ;
e . Handled = true ;
e . Handled = true ;
break ;
break ;
case Key . Enter :
case Key . Enter :