如何将事件处理程序委托转换为具有不同签名的委托

时间:2014-09-11 12:02:15

标签: c# wpf events delegates

我正在编写的代码实际上是一个WPF行为,用于从网格控件中获取所选项(SelectedItems,我们知道,它不是可绑定属性)。我实际上正在使用Telerik RadGridView但我希望行为对于任何具有SelectionChanged事件的内容都是通用的。但是,不同的控件具有SelectionChanged事件处理程序的不同签名(RadGridView使用Telerik.Windows.Controls.SelectionChangeEventArgs,而标准GridView使用System.Windows.Controls.SelectionChangedEventArgs)。我们可以确定的一件事是事件args将从EventArgs派生(实际上我们可以确定它将从RoutedEventArgs派生)。

然而,虽然我可以编写一个通用事件处理程序,它将RoutedEventArgs作为其第二个参数,并且我可以使用反射来获取SelectionChangedEvent的EventInfo,但我无法在不使用精确签名的情况下将处理程序挂钩到事件对于事件处理程序 - 在本例中为RadGridView处理程序。

这是我的代码。我已经包含了所有内容,但重要的是SelectItemPropertyChanged,它是DependencyObject PropertyChangedCallback,它尝试将事件处理程序SelectionChangedHandler连接到SelectionChangedEvent。 (SelectionChangedHandler中的代码与问题无关,但我已经把它留在了,所以我很清楚我在做什么)。

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;
    }
}

我已经尝试了各种方法来使用反射来为处理程序获取正确的签名,从而创建一个正确类型的委托,但我无法使它工作 - AddEventHandler(和RemoveEventHandler)抛出InvalidArgumentException,完整堆栈跟踪如下:

{“'System.Windows.RoutedEventHandler'类型的对象无法转换为'System.EventHandler`1 [Telerik.Windows.Controls.SelectionChangeEventArgs]'的类型。”}

at System.RuntimeType.TryChangeType(Object value,Binder binder,CultureInfo culture,Boolean needsSpecialCast)

有人可以提供建议吗?

1 个答案:

答案 0 :(得分:2)

调用EventHandlerType时,您需要将委托转换为事件AddEventHandler。这是一个示例应用程序:

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);
        }
    }
}