Добавить обработчик событий, используя отражение - PullRequest
0 голосов
/ 02 марта 2020

У меня есть файл .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*/
    }

EventName это имя из UIElement событий класса. MethodToExecute - это имя метода для вызова при возникновении события. Args - это аргументы, переданные в MethodToExecute.

Я не хочу, чтобы мои пользователи могли вызывать какие-либо методы в приложении, поэтому я не использую отражение, чтобы получить MethodInfo вместо этого я создаю Dictionary: Dictionary<string, Delegate> MethodsDictionary. key в этом словаре - это имя метода (MethodToExecute из Command класса), а значение выглядит примерно так:

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

Но я бы хотел сделать динамическую c привязку событий. Ну, конечно, я могу сделать это через некрасивое свойство switch-case на command.Name, но я все же хотел бы попробовать решение с отражением. На мой взгляд, решение должно выглядеть примерно так:

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 с использованием отражения

Добавить обработчик событий используя Reflection? / Получить объект типа?

Но это не помогает мне решить проблему так, как я хочу. Я попробовал некоторые из приведенных выше решений, но у меня они не сработали, но с разными исключениями.

UPD.

Я пытался реализовать привязку обработчика через switch-case, так как я упомянутый выше. Это привело к тому, что этот метод внутри Command class:

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

Мне не нравится, что эта часть с добавлением новых обработчиков является просто копией вставки предыдущего раздела, но я не вижу другого Обходной путь в этой ситуации. Я хотел бы использовать отражение в этом случае, но я не знаю, возможно ли это.

1 Ответ

0 голосов
/ 03 марта 2020

Если вы не можете заставить отражение работать, вы можете использовать другой Dictionary для хранения поддерживаемых методов подписчика на события:

Command.cs

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

Вы наверняка захотите настроить детали в соответствии с вашим конкретным c сценарием.

Дон не забудьте отписаться от событий (например, определив другое SupportedEventUnsubscriberMap).

...