How do I cast an event handler delegate to one wit

2019-02-25 17:04发布

The code I am writing is actually a WPF behaviour to get the selected items from a grid control (SelectedItems, as we know, is not a bindable property). I am actually using a Telerik RadGridView but I would like the Behaviour to be general for anything with a SelectionChanged event. However, different controls have different signatures for the SelectionChanged event handlers (RadGridView uses Telerik.Windows.Controls.SelectionChangeEventArgs whereas a standard GridView uses System.Windows.Controls.SelectionChangedEventArgs). The one thing we can be sure of is that the event args will be derived from EventArgs (in fact we can be sure that it will be derived from RoutedEventArgs).

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

Here is my code. I've included all of it but the important bit is SelectItemPropertyChanged, which is the DependencyObject PropertyChangedCallback that attempts to hook up the event handler SelectionChangedHandler to the SelectionChangedEvent. (The code in SelectionChangedHandler is irrelevant to the question but I've left it in so it's clear what I'm 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 have tried all sorts of ways to use reflection to get the correct signature for the for the handler, and thereby create a delegate to the right type, but I just can't make it work - AddEventHandler (and RemoveEventHandler) throws an InvalidArgumentException, full stack trace as follows:

{"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 culture, Boolean needsSpecialCast)

Can anyone advise?

1条回答
太酷不给撩
2楼-- · 2019-02-25 17:27

You need to convert delegate to event's EventHandlerType when calling AddEventHandler. Here's a sample app:

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);
        }
    }
}
查看更多
登录 后发表回答