Search code examples
c#wpfweak-eventsweakeventmanager

Is it possible to implement a WeakEventManager for UIElement.LayoutUpdated?


I have an application that consumes LayoutUpdated-events and need to register them weak. Here is the problem, I got stuck on, during implementation of the WeakEventManager

internal class WeakLayoutUpdatedManager : WeakEventManager
{
    [..]

    private void OnLayoutUpdated(object sender, EventArgs e)
    {
        // NOTE: received sender is always null (by design of LayoutUpdated) 
        base.DeliverEvent(sender, e);
    }
}

That's what happens:

  1. we always receive null as sender (by design of LayoutUpdated)
  2. that null gets passed into DeliverEvent
  3. DeliverEvent can not lookup the correct ListenerList, because it needs a sender != null as key

the failing lookup in WPF: WeakEventManager // DeliverEvent // Line: 359

object sourceKey = (sender != null) ? sender : StaticSource;
list = (ListenerList)Table[this, sourceKey];

My question is: is there a way to weak-register to LayoutUpdated event?

I'm not interested in the sender-parameter, so it's ok to me that LayoutUpdated always delivers null (my actual implemention with regular "+=" works). But the WeakEventManager base-class relies on the sender-parameter to keep track of the ListenerLists.


Solution

  • As you have found yourself, null sender breaks WeakEventManager into thinking that this is a static event, which basically makes it incompatible with LayoutUpdated. Since WeakEventManager.DeliverEvent is non-virtual, you cannot really "fix" it.

    So I think your option would be to use a third-party weak event manager, or write one yourself, which shouldn't be that difficult (unless you need a generic solution).

    The idea is to break a hard reference between the event source (UIElement) and the event subscriber (Delegate.Target), which may be achieved by using a mediator class with weak references to the event handler and its target.

    Here is a quick-and-dirty example that would work with events of type EventHandler:

    public class WeakEventListener<TSender>
    {
        private readonly EventHandler _handler;
        private readonly EventInfo _event;
        private readonly WeakReference<object> _target;
    
        // Helps to keep original EventHandler alive as long as its target isn't GCed.
        private readonly ConditionalWeakTable<object, EventHandler> _targetHandler =
            new ConditionalWeakTable<object, EventHandler>();
    
        public WeakEventListener(string eventName, EventHandler handler)
        {
            _handler = new EventHandler(DeliverEvent);
            _event = typeof(TSender).GetEvent(eventName);
            _target = new WeakReference<object>(handler.Target);
            _targetHandler.Add(handler.Target, handler);
        }
    
        public void Add(TSender source) => _event.AddEventHandler(source, _handler);
    
        public void Remove(TSender source) => _event.RemoveEventHandler(source, _handler);
    
        private void DeliverEvent(object sender, EventArgs args)
        {
            if (_target.TryGetTarget(out object target) &&
                _targetHandler.TryGetValue(target, out EventHandler handler))
            {
                handler(sender, args);
            }
        }
    }
    

    Which may be used as follows:

    var listener = new WeakEventListener<UIElement>(nameof(LayoutUpdated), OnLayoutUpdated);
    listener.Add(uiElement);