Search code examples
wpfrouted-events

Passing arguments to RoutedEventHandler


I have a UserControl with a Slider which I'd like to handle RoutedEvent. Button was no problem and it works fine, since there are no args. This is my Button handler:

public static readonly RoutedEvent OnOffClickEvent = EventManager.RegisterRoutedEvent(
     nameof(OnOffClick), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ColorLightControl));
public event RoutedEventHandler OnOffClick
{
     add => AddHandler(OnOffClickEvent, value);
     remove => RemoveHandler(OnOffClickEvent, value);
}

private void ButtonOnOff_Click(object sender, RoutedEventArgs e) =>
     RaiseEvent(new RoutedEventArgs(OnOffClickEvent));

But banging my head on the table because can't handle Slider's ValueChanged event. This is what I coded but it doesn't work on execution.

public static readonly RoutedEvent ColorSlideEvent = EventManager.RegisterRoutedEvent(
     nameof(ColorSlide), RoutingStrategy.Bubble, typeof(EventHandler<RoutedPropertyChangedEventArgs<double>>), typeof(ColorLightControl));
public event EventHandler<RoutedPropertyChangedEventArgs<double>> ColorSlide
{
     add => AddHandler(ColorSlideEvent, value);
     remove => RemoveHandler(ColorSlideEvent, value);
}

private void SliderColor_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) =>
     RaiseEvent(new RoutedPropertyChangedEventArgs<double>(e.OldValue, e.NewValue, ColorSlideEvent));

I get this error and I'm pretty sure it's because ColorSlide is not declated as RoutedEventHandler but not sure how to pass args. Any help appreciated. Thanks

System.InvalidCastException: 'Unable to cast object of type 'System.EventHandler1[System.Windows.RoutedPropertyChangedEventArgs1[System.Double]]' to type 'System.Windows.RoutedPropertyChangedEventHandler`1[System.Double]'.'


Solution

  • The error message tells the whole story:
    either use the appropriate event handler RoutedPropertyChangedEventHandler (instead of EventHadler<T>) or create a custom class that extends RoutedEventArgs.

    The reason is that RoutedPropertyChangedEventArgs<T> overrides the virtual RoutedEventArgs.InvokeEventHandler method (which is highly recommended to do so). In this override you usually cast the Delegate to a specific type (in place of reflection). This is where the coupling between RoutedPropertyChangedEventArgs and RoutedPropertyChangedEventHandler is created and where the exception you have encountered is thrown (as a result of the failed explicit type cast).
    Since the default implementation of RoutedEventArgs.InvokeEventHandler uses reflection, you are advised to provide a specialized override to improve the performance (as you eliminate the reflection required to write a generic event invocator).

    An example to create a custom routed event args type including the recommended RoutedEventArgs.InvokeEventHandler override:

    // Optional delegate as an alternative to 'EventHandler<SliderValueChangedRoutedEventArgs<TValue>>'.
    public delegate void SliderValueChangedRoutedEventHandler<TValue>(object sender, SliderValueChangedRoutedEventArgs<TValue> e);
    
    // Routed event args that supports the 'SliderValueChangedRoutedEventHandler<TValue>' delegate
    // and the 'EventHandler<SliderValueChangedRoutedEventArgs<TValue>>' delegate
    public class SliderValueChangedRoutedEventArgs<TValue> : RoutedEventArgs
    {
      public SliderValueChangedRoutedEventArgs()
      {
      }
    
      public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent) : base(routedEvent)
      {
      }
    
      public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)
      {
      }
    
      public SliderValueChangedRoutedEventArgs(RoutedEvent routedEvent, object source, TValue value) : base(routedEvent, source)
      {
        this.Value = value;
      }
    
      protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
      {
        if (genericHandler is EventHandler<SliderValueChangedRoutedEventArgs<TData>> defaultHandler)
        {
          defaultHandler.Invoke(genericTarget, this);
        }
        else
        {
          var strongTypedHandler = (SliderValueChangedRoutedEventHandler<TData>)genericHandler;
          strongTypedHandler.Invoke(genericTarget, this);
        }
      }
    
      public TValue Value { get; }
    }