繁体   English   中英

使用反射添加事件处理程序

[英]Add event handler using reflection

我有一个带有所谓命令的.json文件:

"Commands":[{
   "EventName": "MouseLeftButtonUp",
   "MethodToExecute": "NextJson",
   "Args": "Next.json"
},{
   "EventName": "MouseRightButtonUp",
   "MethodToExecute": "CloseApp"
}

我将此json反序列化为此类:

    public class Command
    {
        [JsonPropertyName("EventName")]
        public string EventName { get; set; }

        [JsonPropertyName("MethodToExecute")]
        public string MethodToExecute { get; set; }

        [JsonPropertyName("Args")]
        public string Args { get; set; }

        /*Methods*/
    }

EventNameUIElement类事件的名称。 MethodToExecute是事件触发时要调用的方法的名称。 Args是传递给MethodToExecute的参数。

我不希望我的用户能够调用应用程序中的任何方法,所以我不使用反射来获取MethodInfo ,而是创建Dictionary : Dictionary<string, Delegate> MethodsDictionary 这本字典中的key是方法的名称(来自Command类的MethodToExecute ),其值是这样的:

MethodsDictionary.Add(nameof(CloseApp), new Action(CloseApp));
MethodsDictionary.Add(nameof(NextJson), new Action<string>(NextJson));

在不使用反射的情况下,我添加了这样的事件处理程序:

button.MouseLeftButtonUp += (sender, args) => MethodsDictionary[command.MethodToExecute].DynamicInvoke(command.Args);

但我想对事件进行动态绑定。 好吧,当然我可以通过command.Name属性上丑陋的switch-case来实现,但我仍然想尝试使用反射的解决方案。 在我看来,解决方案应该是这样的:

foreach (var command in commands)
{
    command.Bind(uielement, MethodsDictionary[command.MethodToExecute]);
}

//And command.Bind method is like:

public void Bind(UIElement uielement, Delegate methodToExecute)
{
    //I know there's no such method like GetEventHandler, just an example
    var handler = uielement.GetEventHandler(EventName);
    handler += (sender, args) => methodToExecute.DynamicInvoke(Args);
}

我搜索了几个非常相似的问题:

使用反射订阅事件

https://social.msdn.microsoft.com/Forums/vstudio/en-US/d7f184f1-0964-412a-8659-6759a0e2db83/c-reflection-problem-subscribing-to-event?forum=netfxbcl

https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-hook-up-a-delegate-using-reflection

使用反射的 AddEventHandler

使用反射添加事件处理程序? / 获取类型的对象?

但是这些并不能帮助我以我想要的方式解决问题。 我尝试了上面的一些解决方案,但它们对我不起作用,因不同的例外而失败。

更新。

如上所述,我尝试通过switch-case实现处理程序绑定。 它导致了Command类中的这个方法:

public void Bind(UIElement element)
{
    switch (this.Name)
    {
        case "MouseRightButtonUp":
        {
            element.MouseRightButtonUp += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
            break;
        }
        case "Click":
        {
            //UIElement doesn't have Click event
            var button = element as ButtonBase;
            button.Click += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
            break;
        }
        /*And so on for each event*/
        default:
        {
            throw new NotSupportedException();
        }
    }
}

我不喜欢,添加新处理程序的那部分只是上一节的复制粘贴,但在这种情况下我没有看到其他解决方法。 我想在这种情况下使用反射,但我不知道是否可能。

如果你不能让反射工作,你可以使用另一个Dictionary来存储支持的事件订阅者方法:

命令文件

public class Command
{
    [JsonPropertyName("EventName")]
    public string EventName { get; set; }

    [JsonPropertyName("MethodToExecute")]
    public string MethodToExecute { get; set; }

    [JsonPropertyName("Args")]
    public string Args { get; set; }

    /*Methods*/
}

JsonEventMapper.cs

class JsonEventMapper
{
  private Dictionary<string, Action<UIElement, EventHandler>>> SupportedEventSubscriberMap { get; }
  private Dictionary<string, EventHandler> RegisteredEventHandlerMap { get; }   

  public JsonEventMapper()
  {
    this.SupportedEventSubscriberMap = new Dictionary<string, Action<UIElement, EventHandler>>() 
    { 
      { 
        nameof(UIElement.MouseRightButtonUp), (uiElement, handler) => uiElement.MouseRightButtonUp += handler.Invoke 
      },
      { 
        nameof(UIElement.LostFocus), (uiElement, eventSubscriber) => uiElement.LostFocus += handler.Invoke 
      }
    };

    this.RegisteredEventHandlerMap = new Dictionary<string, EventHandler>()
    {
      {
        nameof(UIElement.MouseLeftButtonUp), CloseApp
      },
      {
        nameof(UIElement.LostFocus), NextJson
      }
    };
  }

  public void RegisterJsonCommands(IEnumerable<Command> commands, UIElement uiElement)
  {    
    foreach (var command in commands)
    {
      BindCommandToEvent(uiElement, command);
    }
  }

  private void BindCommandToEvent(UIElement uiElement, Command command)
  {    
    if (this.SupportedEventSubscriberMap.TryGetValue(command.EventName, out Action<UIElement, EventHandler> eventSubscriber)
      && this.RegisteredEventHandlerMap.TryGetValue(command.EventName, out EventHandler eventHandler))
    {
      eventSubscriber.Invoke(uiElement, eventHandler);
    }
  }

  private void CloseApp(object sender, EventArgs args)
  {    
    // Handle event
  }

  private void NextJson(object sender, EventArgs args)
  {    
    // Handle event
  }
}

用法

IEnumerable<Command> commands = DeserializeJsonToCommands();
var eventMapper = new JsonEventMapper();
eventMapper.RegisterJsonCommands(commands, button);

您肯定希望根据您的特定场景调整细节。

不要忘记取消订阅事件(例如,通过定义另一个SupportedEventUnsubscriberMap )。

我认为你想要做的是这样的:

public class CommandModel
{
    /// properties

    public void Bind(UIElement element)
    {
        EventBinder.Bind(() => MainViewModel.RunCommand(MethodToExecute, Args), element, EventName);
    }
}

public static class EventBinder
{
    public static void Bind(Action action, object eventSource, string eventName)
    {
        var eventInfo = eventSource.GetType().GetEvent(eventName);

        EventHandler eventHandler = (s, e) => action();

        eventInfo.AddEventHandler(eventSource, ConvertDelegate(eventHandler, eventInfo.EventHandlerType));
    }

    private static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
    {
        return Delegate.CreateDelegate(
            targetDelegateType,
            originalDelegate.Target,
            originalDelegate.Method);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM