fix(dock): address subscription leaks and selection rerender issues

This commit is contained in:
2026-03-28 13:10:08 +09:00
parent 038a13bbe0
commit 944687848e

View File

@@ -17,6 +17,8 @@ public sealed partial class DockLayout : Control
{ {
private const string PART_ROOT_GRID = "PART_RootGrid"; private const string PART_ROOT_GRID = "PART_RootGrid";
private readonly HashSet<DockNode> _subscribedNodes = new();
public DockLayout() public DockLayout()
{ {
DefaultStyleKey = typeof(DockLayout); DefaultStyleKey = typeof(DockLayout);
@@ -35,10 +37,7 @@ public sealed partial class DockLayout : Control
private void OnUnloaded(object sender, RoutedEventArgs e) private void OnUnloaded(object sender, RoutedEventArgs e)
{ {
if (Root != null) UnsubscribeFromAll();
{
UnsubscribeFromNode(Root);
}
} }
public DockGroupNode? Root public DockGroupNode? Root
@@ -54,10 +53,7 @@ public sealed partial class DockLayout : Control
{ {
if (d is DockLayout layout) if (d is DockLayout layout)
{ {
if (e.OldValue is DockGroupNode oldRoot) layout.UnsubscribeFromAll();
{
layout.UnsubscribeFromNode(oldRoot);
}
if (e.NewValue is DockGroupNode newRoot && layout.IsLoaded) if (e.NewValue is DockGroupNode newRoot && layout.IsLoaded)
{ {
@@ -70,6 +66,11 @@ public sealed partial class DockLayout : Control
private void SubscribeToNode(DockNode node) private void SubscribeToNode(DockNode node)
{ {
if (!_subscribedNodes.Add(node))
{
return;
}
node.PropertyChanged += OnNodePropertyChanged; node.PropertyChanged += OnNodePropertyChanged;
if (node is DockGroupNode groupNode) if (node is DockGroupNode groupNode)
@@ -84,6 +85,11 @@ public sealed partial class DockLayout : Control
private void UnsubscribeFromNode(DockNode node) private void UnsubscribeFromNode(DockNode node)
{ {
if (!_subscribedNodes.Remove(node))
{
return;
}
node.PropertyChanged -= OnNodePropertyChanged; node.PropertyChanged -= OnNodePropertyChanged;
if (node is DockGroupNode groupNode) if (node is DockGroupNode groupNode)
@@ -96,12 +102,25 @@ public sealed partial class DockLayout : Control
} }
} }
private void UnsubscribeFromAll()
{
// Copy to array to avoid modification during enumeration
var nodes = _subscribedNodes.ToArray();
foreach (var node in nodes)
{
node.PropertyChanged -= OnNodePropertyChanged;
if (node is DockGroupNode groupNode)
{
((INotifyCollectionChanged)groupNode.Children).CollectionChanged -= OnChildrenCollectionChanged;
}
}
_subscribedNodes.Clear();
}
private void OnNodePropertyChanged(object? sender, PropertyChangedEventArgs e) private void OnNodePropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
// Filter to relevant property names // Filter to structural property names
if (e.PropertyName == nameof(DockGroupNode.Orientation) || if (e.PropertyName == nameof(DockGroupNode.Orientation))
e.PropertyName == nameof(DockPanelNode.SelectedIndex) ||
e.PropertyName == nameof(DockPanelNode.SelectedItem))
{ {
RenderTree(); RenderTree();
} }
@@ -112,10 +131,10 @@ public sealed partial class DockLayout : Control
if (e.Action == NotifyCollectionChangedAction.Reset) if (e.Action == NotifyCollectionChangedAction.Reset)
{ {
// On Reset, we don't know what was removed. // On Reset, we don't know what was removed.
// Simplest is to unsubscribe from everything and resubscribe from Root. // We must unsubscribe from everything and resubscribe from Root to avoid leaks.
UnsubscribeFromAll();
if (Root != null) if (Root != null)
{ {
UnsubscribeFromNode(Root);
SubscribeToNode(Root); SubscribeToNode(Root);
} }
} }
@@ -175,11 +194,11 @@ public sealed partial class DockLayout : Control
var childUI = CreateUIForNode(children[i]); var childUI = CreateUIForNode(children[i]);
if (groupNode.Orientation == Orientation.Horizontal) if (groupNode.Orientation == Orientation.Horizontal)
{ {
Grid.SetColumn(childUI as FrameworkElement, i); Grid.SetColumn((FrameworkElement)childUI, i);
} }
else else
{ {
Grid.SetRow(childUI as FrameworkElement, i); Grid.SetRow((FrameworkElement)childUI, i);
} }
grid.Children.Add(childUI); grid.Children.Add(childUI);
} }