如何将事件处理程序委托给具有不同签名的委托 [英] How do I cast an event handler delegate to one with a different signature
问题描述
然而,而我可以编写一个通用事件处理程序,它将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屋!