[英]Add event handler using reflection
I have a .json
file with so-called commands:我有一个带有所谓命令的
.json
文件:
"Commands":[{
"EventName": "MouseLeftButtonUp",
"MethodToExecute": "NextJson",
"Args": "Next.json"
},{
"EventName": "MouseRightButtonUp",
"MethodToExecute": "CloseApp"
}
I deserialize this json to this class:我将此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
is a name of UIElement
class events. EventName
是UIElement
类事件的名称。 MethodToExecute
is a name of method to call, when event triggered. MethodToExecute
是事件触发时要调用的方法的名称。 Args
are the args, passed to the MethodToExecute
. Args
是传递给MethodToExecute
的参数。
I don't want my users to be able to call any method in the application, so I don't use reflection to get MethodInfo
, instead I create Dictionary
: Dictionary<string, Delegate> MethodsDictionary
.我不希望我的用户能够调用应用程序中的任何方法,所以我不使用反射来获取
MethodInfo
,而是创建Dictionary
: Dictionary<string, Delegate> MethodsDictionary
。 The key
in this dictionary is the name of method ( MethodToExecute
from Command
class), and the value is something like this:这本字典中的
key
是方法的名称(来自Command
类的MethodToExecute
),其值是这样的:
MethodsDictionary.Add(nameof(CloseApp), new Action(CloseApp));
MethodsDictionary.Add(nameof(NextJson), new Action<string>(NextJson));
Without using reflection, I'd added the event handler like this:在不使用反射的情况下,我添加了这样的事件处理程序:
button.MouseLeftButtonUp += (sender, args) => MethodsDictionary[command.MethodToExecute].DynamicInvoke(command.Args);
But I'd like to make a dynamic binding of events.但我想对事件进行动态绑定。 Well, of course I can make it through ugly
switch-case
on command.Name
property, but I still would like to try the solution with reflection.好吧,当然我可以通过
command.Name
属性上丑陋的switch-case
来实现,但我仍然想尝试使用反射的解决方案。 The solutuion, as I see it, should looke something like:在我看来,解决方案应该是这样的:
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);
}
I searched through several pretty similar questions:我搜索了几个非常相似的问题:
Subscribe to an event with Reflection 使用反射订阅事件
https://social.msdn.microsoft.com/Forums/vstudio/en-US/d7f184f1-0964-412a-8659-6759a0e2db83/c-reflection-problem-subscribing-to-event?forum=netfxbcl 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 https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-hook-up-a-delegate-using-reflection
AddEventHandler using reflection 使用反射的 AddEventHandler
Add Event Handler using Reflection ? 使用反射添加事件处理程序? / Get object of type?
/ 获取类型的对象?
But these doesn't help me to solve the problem the way I want to.但是这些并不能帮助我以我想要的方式解决问题。 I tried some of the solutions above, but they didn't work out for me, failing with different exceptions.
我尝试了上面的一些解决方案,但它们对我不起作用,因不同的例外而失败。
UPD.更新。
I tried to implement handler binding through switch-case
, as I mentioned above.如上所述,我尝试通过
switch-case
实现处理程序绑定。 It resulted in this method inside Command
class:它导致了
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();
}
}
}
I don't like, how that part with adding new handlers is just a copy-paste of previous section, but I don't see another workaround in this situation.我不喜欢,添加新处理程序的那部分只是上一节的复制粘贴,但在这种情况下我没有看到其他解决方法。 I'd like to use the reflection in this case, but I don't know if it's possible.
我想在这种情况下使用反射,但我不知道是否可能。
If you can't get reflection to work, you can use another Dictionary
to store supported event subscriber methods:如果你不能让反射工作,你可以使用另一个
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 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);
You surely want to adjust details to your specific scenario.您肯定希望根据您的特定场景调整细节。
Don't forget to unsubscribe from the events (eg, by defining another SupportedEventUnsubscriberMap
).不要忘记取消订阅事件(例如,通过定义另一个
SupportedEventUnsubscriberMap
)。
I think what you're trying to do is something like that:我认为你想要做的是这样的:
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.