Browse Source

Raise atomic ranged events from the tree flattener

NodesInserted/NodesRemoved reported a multi-node change (a node plus its visible
descendants -- a contiguous run) as a sequence of single-item Add/Remove events,
but the underlying tree updates Count by the WHOLE run before they fire. A
consumer that reconciles the change one item at a time and re-indexes the source
mid-sequence then reads against a Count that has already dropped by the full run:
Avalonia's SelectionModel does exactly this (it re-reads SelectedItems while
handling each Remove), indexing past the end and throwing ArgumentOutOfRangeException
out of TreeFlattener's indexer whenever a selection was live during a bulk reshape
(e.g. toggling UseNestedNamespaceNodes). It only surfaced under full-suite timing,
which is why it read as flaky.

Raise one ranged event for the whole run instead, so the notification matches the
tree's state in a single step and Count/indices stay in agreement. The indexer
keeps throwing on genuine out-of-range access. Avalonia 12.4's virtualization and
selection handle ranged Add/Remove (every expand/collapse exercises it); full
headless suite is green.
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
4585be8de9
  1. 20
      ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs

20
ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs

@ -52,24 +52,28 @@ namespace ICSharpCode.ILSpyX.TreeView @@ -52,24 +52,28 @@ namespace ICSharpCode.ILSpyX.TreeView
CollectionChanged?.Invoke(this, e);
}
// A node plus its visible descendants form one contiguous run in the flattened list, and the
// underlying tree's Count is updated by the WHOLE run before these fire. Raise a single ranged
// event so the notification matches the tree: Count and indices stay in agreement for a
// consumer that reconciles the change in one step. Per-node events instead leave the source
// already fully resized while the consumer is still mid-sequence, so one that re-indexes during
// reconciliation (Avalonia's SelectionModel re-reading SelectedItems) reads past the end.
public void NodesInserted(int index, IEnumerable<SharpTreeNode> nodes)
{
if (!includeRoot)
index--;
foreach (SharpTreeNode node in nodes)
{
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, index++));
}
IList list = nodes as IList ?? new List<SharpTreeNode>(nodes);
if (list.Count > 0)
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
}
public void NodesRemoved(int index, IEnumerable<SharpTreeNode> nodes)
{
if (!includeRoot)
index--;
foreach (SharpTreeNode node in nodes)
{
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, node, index));
}
IList list = nodes as IList ?? new List<SharpTreeNode>(nodes);
if (list.Count > 0)
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, list, index));
}
public void Stop()

Loading…
Cancel
Save