Unplanned
Last Updated: 31 Jul 2025 12:28 by Martin Ivanov
Martin Ivanov
Created on: 31 Jul 2025 12:28
Category: Docking
Type: Bug Report
0
Docking: Memory leak in the PaneSource collection when using INotifyCollectionChanged and switching user sessions on the computer or connecting to a remote desktop session

When using an INotifyCollectionChanged (like ObservableCollection<T>), RadDocking subscribes to its CollectionChanged event. This happens on PropertyChanged of the PaneSource property. 

It seems that WPF re-intializes the application when you connect to a running remote desktop session or switch the user to a session where the corresponding WPF app is already opened. This triggers the PropertyChanged event again, which subscribes to the PaneSource collection again. 

If you connect to the session multiple times, the CollectionChanged handler will be attached multiple times leading to a memory leak.

To work this around, you can manually unsubscribe from the CollectionChanged event when multiple handlers are added.

public class CustomDocking : RadDocking
{
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property.Name == nameof(PanesSource) && PanesSource is INotifyCollectionChanged observableCollection)
        {
            UnsubscribeCollectionChanged(observableCollection, this);
        }
    }

    public static void UnsubscribeCollectionChanged(INotifyCollectionChanged collection, object dockingInstance)
    {
        var handlerMethod = typeof(RadDocking).GetMethod("OnPanesSourceCollectionChanged", BindingFlags.NonPublic | BindingFlags.Instance);                        
        var handlerDelegate = Delegate.CreateDelegate(typeof(NotifyCollectionChangedEventHandler), dockingInstance, handlerMethod);

        var field = collection.GetType().GetField("CollectionChanged", BindingFlags.Instance | BindingFlags.NonPublic);
        var eventDelegate = (MulticastDelegate)field.GetValue(collection);

        var handlers = eventDelegate?.GetInvocationList().Where(d => d.Method == handlerMethod);
        bool shouldRemoveHandler = handlers.Count() > 1;                                
        if (shouldRemoveHandler)
        {
            typeof(INotifyCollectionChanged)
                .GetEvent("CollectionChanged")
                ?.RemoveMethod
                ?.Invoke(collection, new object[] { handlerDelegate });
        }
    }
}

 

0 comments