如何将事件处理程序委托给具有不同签名的委托 [英] How do I cast an event handler delegate to one with a different signature

查看:87
本文介绍了如何将事件处理程序委托给具有不同签名的委托的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在写的代码实际上是一个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)
{
//对于所有使用SelectionChanged事件的类,没有共同的基础,所以使用反射
EventInfo selectionChangedEventInfo = dependencyObject.GetType()。GetEvent(SelectionChanged);
if(selectionChangedEventInfo == null)
{
throw new ArgumentException(必须有SelectionChanged事件);
}

如果((bool)dependencyPropertyChangedEventArgs.OldValue)
{
//这是我想要做的,但是由于处理程序签名错误
selectionChangedEventInfo.RemoveEventHandler(dependencyObject,(RoutedEventHandler)SelectionChangedHandler);

//这样工作正常,因为它是RadGridView的正确类型但不是一般的
//selectionChangedEventInfo.RemoveEventHandler(dependencyObject,(EventHandler< Telerik.Windows.Controls.SelectionChangeEventArgs> )SelectionChangedHandler);
}
如果((bool)dependencyPropertyChangedEventArgs.NewValue)
{
//这是我想要做的,但它抛出因为处理程序签名错误
selectionChangedEventInfo.AddEventHandler(dependencyObject,(RoutedEventHandler)SelectionChangedHandler);

//这样工作正常,因为它是RadGridView的正确类型但不是一般的
//selectionChangedEventInfo.AddEventHandler(dependencyObject,(EventHandler< Telerik.Windows.Controls.SelectionChangeEventArgs> )SelectionChangedHandler);
}
}

private static void SelectionChangedHandler(object sender,RoutedEventArgs eventArgs)
{
//对于已添加项/ RemovedItems的所有类,没有公共基础(例如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(必须有AddedItems和RemovedItems);
}
foreach(对象项在(IEnumerable)addedItemsInfo.GetValue(eventArgs,null))
{
((ISelectable)项).IsSelected = true;
}
foreach((IEnumerable)中的对象项removeItemsInfo.GetValue(eventArgs,null))
{
((ISelectable)项).IsSelected = false;
}
}

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



{System.Windows.RoutedEventHandler类型的对象不能转换为类型' System.EventHandler`1 [Telerik.Windows.Controls.SelectionChangeEventArgs]'。。



在System.RuntimeType.TryChangeType(Object value,Binder binder,CultureInfo culture,Boolean needSpecialCast)



任何人都可以建议?

解决方案

调用 AddEventHandler 时,委托给事件的 EventHandlerType 。这是一个示例应用程序:

  using System; 
使用System.Reflection;
使用System.Threading;

命名空间App
{
类程序
{
public event EventHandler< ThreadExceptionEventArgs> Ë;

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

private void Run()
{
EventInfo e = typeof(Program).GetEvent(E);
EventHandler untypedHandler = OnE;
委派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);
}
}
}


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?

解决方案

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

这篇关于如何将事件处理程序委托给具有不同签名的委托的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆