Browse Source

Pass live root.Children to grid so unload removes the row

Assisted-by: Claude:claude-opus-4-7:Claude Code

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
03cc5aba73
  1. 12
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 14
      ILSpy/AssemblyTree/AssemblyListPane.axaml.cs

12
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -975,6 +975,8 @@ public class AssemblyTreeTests @@ -975,6 +975,8 @@ public class AssemblyTreeTests
grid.Focus();
Dispatcher.UIThread.RunJobs();
int itemsBefore = (grid.ItemsSource as System.Collections.ICollection)?.Count ?? -1;
// Act — headless keyboard event simulating the user pressing Del while the tree has
// focus.
global::Avalonia.Headless.HeadlessWindowExtensions.KeyPress(
@ -984,7 +986,11 @@ public class AssemblyTreeTests @@ -984,7 +986,11 @@ public class AssemblyTreeTests
global::Avalonia.Input.PhysicalKey.Delete,
null);
// Assert — the sacrificial assembly is gone from the list; the survivor remains.
// Assert — the sacrificial assembly is gone from the data list AND the grid's bound
// ItemsSource drops by one. Both halves matter: a passing AssemblyList assertion alone
// would mask regressions where the data updates but the grid stays bound to a stale
// snapshot (which is exactly what happened when the BindTree filter materialised the
// children into a List instead of forwarding the live ObservableCollection).
await Waiters.WaitForAsync(() =>
!vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.Any(a => string.Equals(a.ShortName, sacrificialName, System.StringComparison.Ordinal)));
@ -992,6 +998,10 @@ public class AssemblyTreeTests @@ -992,6 +998,10 @@ public class AssemblyTreeTests
vm.AssemblyTreeModel.AssemblyList!.GetAssemblies()
.Should().Contain(a => a.ShortName == survivorName,
"only the selected assembly should be removed");
await Waiters.WaitForAsync(
() => (grid.ItemsSource as System.Collections.ICollection)?.Count < itemsBefore,
description: "DataGrid item count should drop after the assembly is unloaded");
}
[AvaloniaTest]

14
ILSpy/AssemblyTree/AssemblyListPane.axaml.cs

@ -352,10 +352,10 @@ namespace ILSpy.AssemblyTree @@ -352,10 +352,10 @@ namespace ILSpy.AssemblyTree
}
}
// Apply the active LanguageSettings filter (if any). Returns the children unfiltered when
// no settings are available (design-time / unit-host scenarios) so the tree still
// renders. Materialises into a List so each call returns a stable snapshot the grid
// can iterate twice without re-evaluating.
// Apply the active LanguageSettings filter to a child collection materialised on
// expansion. Returns a fresh List so iteration is stable; live updates at deeper levels
// are not preserved (members are realised once per expansion — adds/removes mid-expand
// don't happen in practice).
static IEnumerable<SharpTreeNode> FilterChildren(IEnumerable<SharpTreeNode> children, LanguageSettings? settings)
{
if (settings == null)
@ -383,7 +383,11 @@ namespace ILSpy.AssemblyTree @@ -383,7 +383,11 @@ namespace ILSpy.AssemblyTree
};
var hierarchicalModel = new HierarchicalModel<SharpTreeNode>(options);
hierarchicalModel.SetRoots(FilterChildren(root.Children, settings));
// Pass the live ObservableCollection at the root so the grid observes
// CollectionChanged when assemblies are unloaded / opened. Filter is not applied
// at this level (AssemblyTreeNode never reports Hidden); deeper levels filter
// on expansion via ChildrenSelector.
hierarchicalModel.SetRoots(root.Children);
TreeGrid.HierarchicalModel = hierarchicalModel;
}

Loading…
Cancel
Save