【问题标题】:How do I cast an event handler delegate to one with a different signature如何将事件处理程序委托转换为具有不同签名的委托
【发布时间】:2014-11-05 08:58:44
【问题描述】:

我正在编写的代码实际上是一种 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, CultureInfoculture, Boolean needsSpecialCast)

谁能给点建议?

【问题讨论】:

  • 您说“它会抛出”——请您提供完整的堆栈跟踪信息吗? (或者如果它是一个编译时错误,那么给出确切的错误消息——在这种情况下“抛出”并不合适。)如果你可以将重要部分提取到一个简短但完整的程序中,它也会变得更容易- 它根本不需要是 WPF。
  • 它抛出(如果是编译器错误,我不会说抛出:-))例外是:System.ArgumentException {“'System.Windows.RoutedEventHandler'类型的对象无法转换在 System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfoculture, Boolean needsSpecialCast) 处键入 'System.EventHandler`1[Telerik.Windows.Controls.SelectionChangeEventArgs]'."} 我会尝试将其放入程序中,但正如我所说,重要的是 SelectItemPropertyChanged
  • (如何在这些 cmets 中添加换行符???)
  • 您不能,但您应该将详细信息编辑到问题中而不是 cmets。

标签: c# wpf events delegates


【解决方案1】:

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

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

【讨论】:

  • 这有一个潜在的问题 - 如果您想在另一种方法中删除事件处理程序。我从来没有完全理解 C# 如何在事件处理程序上定义相等,但我知道如果您尝试删除一个并且它与之前添加的一个不“相等”,那么它不会被删除。我认为解决方案是让 typedHandler 成为成员字段,但我会对任何其他意见感兴趣。
  • @Dave 是的,您需要存储 typedHandler 才能稍后退订。顺便说一句,您还可以投票给答案。
  • 当您可以使用 new 创建新委托时,为什么还要麻烦反射?
猜你喜欢
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-24
  • 2019-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多