Search code examples
wpfweak-eventsweakeventmanager

Microsoft.Win32.SystemEvents events don't work with WeakEventManager


When I do

WeakEventManager<SystemEvents, EventArgs>
    .AddHandler(null, nameof(SystemEvents.DisplaySettingsChanged), OnDisplaySettingsChanged);

My OnDisplaySettingsChanged never gets called. However, if I instead use normal event subscribtion via SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged everything works fine.

What's going on?


Solution

  • Turns out it's WeakEventManager's fault. When the event is fired, it implies that source will be null for static event sources (code excerpt from the reference source):

    protected void DeliverEvent(object sender, EventArgs args)
    {
        ListenerList list;
        object sourceKey = (sender != null) ? sender : StaticSource;
        ...
    

    But sender is never null for SystemEvents. Instead it passes a private instance of SystemEvents, WeakEventManager then assumes it's another instance it didn't previously know about and doesn't call the handler.

    Here's the workaround I came up with:

    class EventProxy
    {
        private readonly Action<EventHandler> _subscribe;
        private readonly Action<EventHandler> _unsubscribe;
    
        public EventProxy(Action<EventHandler> subscribe, Action<EventHandler> unsubscribe)
        {
            _subscribe = subscribe;
            _unsubscribe = unsubscribe;
        }
    
        private EventHandler _event;
        public event EventHandler Event
        {
            add
            {
                if (_event == null)
                    _subscribe(OnEvent);
                _event += value;
            }
            remove
            {
                // ReSharper disable once DelegateSubtraction
                _event -= value;
                if (_event == null)
                    _unsubscribe(OnEvent);
            }
        }
    
        private void OnEvent(object sender, EventArgs args)
        {
            _event?.Invoke(this, args);
        }
    }
    

    Usage example:

    var proxy = new EventProxy(h => SystemEvents.DisplaySettingsChanged += h, h => SystemEvents.DisplaySettingsChanged -= h);
    WeakEventManager<EventProxy, EventArgs>.AddHandler(proxy, nameof(EventProxy.Event), OnDisplaySettingsChanged);
    

    Some explanation:

    • SystemEvents holds a strong reference to EventProxy, which holds a weak reference to the handler (via WeakEventManager)
    • When WeakEventManager subscribes to the event inside AddHandler, the proxy subscribes to the original event
    • EventProxy acts as a proxy between the static event and the handler, invoking the handler whenever the original event fires
    • After the handler gets collected, WeakEventManager will eventually run a cleanup, discover that the handler is dead and unsubscribe
    • This will cause the proxy to unsubscribe from the original event, and, eventually, get collected by GC