How can I pass an event handler delegate to one with a different signature

The code I'm writing is actually WPF's behavior to get selected items from a grid control (SelectedItems, as we know, is not a binding property). I am actually using Telerik RadGridView, but I would like the behavior to be common to anything with the SelectionChanged event. However, different controls have different signatures for SelectionChanged event handlers (RadGridView uses Telerik.Windows.Controls.SelectionChangeEventArgs, while the standard GridView uses System.Windows.Controls.SelectionChangedEventArgs). The only thing we can be sure of is that args events will be received from EventArgs (in fact, we can be sure that it will be received from RoutedEventArgs).

However, while I can write a generic event handler that takes RoutedEventArgs as a second parameter, and I can use reflection to get the EventInfo for the SelectionChangedEvent, I cannot hook the handler to the event without using the exact signature for the event handler - in this case, the RadGridView handler.

Here is my code. I've included all of this, but the important bit is SelectItemPropertyChanged, which is a DependencyObject PropertyChangedCallback that tries to bind the SelectionChangedHandler event handler to the SelectionChangedEvent. (The code in SelectionChangedHandler is irrelevant to the question, but I left it behind so I can understand what I am doing).

public static class SelectedItemsChangedBehaviour{
public static readonly DependencyProperty SelectItemsProperty =
    DependencyProperty.RegisterAttached("SelectItems", typeof(bool), typeof(SelectedItemsChangedBehaviour),
    new FrameworkPropertyMetadata(false, new PropertyChangedCallback(SelectItemPropertyChanged)));

public static void SetSelectItems(DependencyObject dependencyObject, bool selectItems)
{
    dependencyObject.SetValue(SelectItemsProperty, selectItems);
}

public static bool GetSelectItems(DependencyObject dependencyObject)
{
    return (bool)dependencyObject.GetValue(SelectItemsProperty);
}

private static void SelectItemPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    // No common base for all classes with SelectionChanged event so use reflection
    EventInfo selectionChangedEventInfo = dependencyObject.GetType().GetEvent("SelectionChanged");
    if (selectionChangedEventInfo == null)
    {
        throw new ArgumentException("Must have a SelectionChanged event.");
    }

    if ((bool)dependencyPropertyChangedEventArgs.OldValue)
    {
        // This is what I want to do, but it throws because the handler signature is wrong
        selectionChangedEventInfo.RemoveEventHandler(dependencyObject, (RoutedEventHandler)SelectionChangedHandler);

        // This works fine because it is of the right type for the RadGridView but is not general
        //selectionChangedEventInfo.RemoveEventHandler(dependencyObject, (EventHandler<Telerik.Windows.Controls.SelectionChangeEventArgs>)SelectionChangedHandler);
    }
    if ((bool)dependencyPropertyChangedEventArgs.NewValue)
    {
        // This is what I want to do, but it throws because the handler signature is wrong
        selectionChangedEventInfo.AddEventHandler(dependencyObject, (RoutedEventHandler)SelectionChangedHandler);

        // This works fine because it is of the right type for the RadGridView but is not general
        //selectionChangedEventInfo.AddEventHandler(dependencyObject, (EventHandler<Telerik.Windows.Controls.SelectionChangeEventArgs>)SelectionChangedHandler);
    }
}

private static void SelectionChangedHandler(object sender, RoutedEventArgs eventArgs)
{
    // No common base for all classes with AddedItems/RemovedItems (eg. System.Windows.Controls.SelectionChangedEventArgs / Telerik.Windows.Controls.SelectionChangeEventArgs
    PropertyInfo addedItemsInfo = eventArgs.GetType().GetProperty("AddedItems");
    PropertyInfo removedItemsInfo = eventArgs.GetType().GetProperty("RemovedItems");
    if (addedItemsInfo == null || removedItemsInfo == null)
    {
        throw new ArgumentException("Must have AddedItems and RemovedItems");
    }
    foreach (object item in (IEnumerable)addedItemsInfo.GetValue(eventArgs, null))
    {
        ((ISelectable)item).IsSelected = true;
    }
    foreach (object item in (IEnumerable)removedItemsInfo.GetValue(eventArgs, null))
    {
        ((ISelectable)item).IsSelected = false;
    }
}

      

I've tried all sorts of ways to use reflection to get the correct signature for the handler, and thus create a delegate in the type I want, but I just can't seem to get it to work - AddEventHandler (and RemoveEventHandler) throws InvalidArgumentException, full stack trace like this:

{"Object of type" System.Windows.RoutedEventHandler "cannot be converted to type" System.EventHandler`1 [Telerik.Windows.Controls.SelectionChangeEventArgs] ".}

at System.RuntimeType.TryChangeType (Object Value, Binder, Binder, CultureInfo, Boolean NeedsSpecialCast)

Anyone have any advice?

+3


source to share


1 answer


You need to convert the delegate to an event EventHandlerType

when called AddEventHandler

. Here's a sample application:



using System;
using System.Reflection;
using System.Threading;

namespace App
{
    class Program
    {
        public event EventHandler<ThreadExceptionEventArgs> E;

        static void Main ()
        {
            new Program().Run();
        }

        private void Run ()
        {
            EventInfo e = typeof(Program).GetEvent("E");
            EventHandler untypedHandler = OnE;
            Delegate typedHandler = Delegate.CreateDelegate(e.EventHandlerType,
                untypedHandler.Target, untypedHandler.Method);
            e.AddEventHandler(this, typedHandler);
            E(this, new ThreadExceptionEventArgs(new Exception("Hello world!")));
        }

        private void OnE (object sender, EventArgs args)
        {
            Console.WriteLine(((ThreadExceptionEventArgs)args).Exception.Message);
        }
    }
}

      

+2


source







All Articles