通过反射删除路由事件处理程序?
背景:我正在使用 WPF 和 C# (3.5),并且正在开发一个应用程序,该应用程序允许用户查看已经是已编译程序集一部分的表单/窗口/用户控件。 当他们查看它时,他们应该能够单击任何控件(按钮、文本框,甚至标签),控件旁边应该出现一个小的弹出编辑器,然后他们可以在其中输入该控件的工具提示、helpID 等。
总而言之:我需要模仿 WPF 中的基本设计视图。 这意味着我至少需要执行以下操作:
- 从给定程序集加载用户控件/窗口(没问题)
- 将其实例化为用户控件/窗口(没问题)
- 清除所有控件的所有订阅事件处理程序
- 将我自己的“ShowEditorPopup”事件处理程序分配给每个控件(应该不是问题)
首先,如果有人有关于更简单或更好的建议要走的路线,请告诉我。 (显然,WPF 没有 DesignHost 类型的组件(就像我读过的 .NET 2 那样),所以就这样了。)
我被困在粗体项目上 - 清除任何订阅的 EventHandler。 在深入研究并进入 Reflector 之后,我想出了这段很酷的危险代码(在这里,我只是试图获取 XAML 中定义的名为 someButton 的单个按钮的所有事件处理程序):
<Button Name="someButton" Click="someButton_Click"/>
这是代码(如果需要,您可以从 someButton_Click 事件处理程序运行它):
public void SomeFunction()
{
// Get the control's Type
Type someButtonType = ((UIElement)someButton).GetType();
// Dig out the undocumented (yes, I know, it's risky) EventHandlerStore
// from the control's Type
PropertyInfo EventHandlersStoreType =
someButtonType.GetProperty("EventHandlersStore",
BindingFlags.Instance | BindingFlags.NonPublic);
// Get the actual "value" of the store, not just the reflected PropertyInfo
Object EventHandlersStore = EventHandlersStoreType.GetValue(someButton, null);
// Get the store's type ...
Type storeType = EventHandlersStore.GetType();
// ... so we can pull out the store's public method GetRoutedEventHandlers
MethodInfo GetEventHandlers =
storeType.GetMethod("GetRoutedEventHandlers",
BindingFlags.Instance | BindingFlags.Public);
// Reflector shows us that the method's sig is this:
// public RoutedEventHandlerInfo[] GetRoutedEventHandlers(RoutedEvent routedEvent);
// So set up the RoutedEvent param
object[] Params = new object[] { ButtonBase.ClickEvent as RoutedEvent };
// I've also seen this for the param, but doesn't seem to make a difference:
// object[] Params = new object[] { someButton.ClickEvent };
// And invoke it ... and watch it crash!
GetEventHandlers.Invoke(someButton, Params);
}
它可以执行到调用,返回:对象与目标类型不匹配(即,我的参数或目标对象混乱)。 我发现您可以通过以下方式解决此问题:
GetEventHandlers.Invoke(Activator.CreateInstance(someButton.GetType()), Params);
// Also doesn't work...
当我在 GetEventHandlers MethodInfo 上设置监视时,它看起来很棒,只是不喜欢我在调用 Invoke 时传递的内容。
我觉得我正处于如何获取 RoutedEvent 处理程序列表的最后一步(就像旧的 GetInitationList() 一样,显然它不适用于 WPF RoutedEvents)。 从那里开始,从每个控件中删除这些处理程序并获得无事件形式就足够简单了,然后我可以向其中添加自己的事件。
有什么线索吗? 再次强调,如果有更好/更简单的方法来完成整个任务,请告诉我:)
Background: I'm using WPF and C# (3.5) and am working on an app that allows a user to view a form/window/usercontrol that's already part of a compiled assembly. When they view it, they should be able to click on any control (buttons, textboxes, even labels), a little popup editor should appear by the control where they can then type in a tooltip, helpID, etc., for that control.
The long and short of it: I need to imitate a basic design view in WPF. Which means I need to do at least the following:
- Load the usercontrol/window from a given assembly (no problem)
- Instantiate it the usercontrol/window (no problem)
- Clear all subscribed EventHandlers for all its controls
- Assign my own "ShowEditorPopup" EventHandler to each control (shouldn't be a problem)
First off, if anybody has suggestions on an easier or better route to take, please let me know. (Apparently there is no DesignHost kind of component for WPF (like I've read .NET 2 has), so that's out.)
I'm stuck on the bolded item - clearing any subscribed EventHandlers. After digging around some and getting into Reflector, I've come up with this cool chunk of dangerous code (here, I'm just trying to get all the EventHandlers for a single Button called someButton defined in the XAML):
<Button Name="someButton" Click="someButton_Click"/>
Here's the code (you can run it from the someButton_Click eventHandler if you want):
public void SomeFunction()
{
// Get the control's Type
Type someButtonType = ((UIElement)someButton).GetType();
// Dig out the undocumented (yes, I know, it's risky) EventHandlerStore
// from the control's Type
PropertyInfo EventHandlersStoreType =
someButtonType.GetProperty("EventHandlersStore",
BindingFlags.Instance | BindingFlags.NonPublic);
// Get the actual "value" of the store, not just the reflected PropertyInfo
Object EventHandlersStore = EventHandlersStoreType.GetValue(someButton, null);
// Get the store's type ...
Type storeType = EventHandlersStore.GetType();
// ... so we can pull out the store's public method GetRoutedEventHandlers
MethodInfo GetEventHandlers =
storeType.GetMethod("GetRoutedEventHandlers",
BindingFlags.Instance | BindingFlags.Public);
// Reflector shows us that the method's sig is this:
// public RoutedEventHandlerInfo[] GetRoutedEventHandlers(RoutedEvent routedEvent);
// So set up the RoutedEvent param
object[] Params = new object[] { ButtonBase.ClickEvent as RoutedEvent };
// I've also seen this for the param, but doesn't seem to make a difference:
// object[] Params = new object[] { someButton.ClickEvent };
// And invoke it ... and watch it crash!
GetEventHandlers.Invoke(someButton, Params);
}
It works up to the Invoke, which returns: Object does not match target type (ie, my params or target object are messed). I've found you can resolve this with:
GetEventHandlers.Invoke(Activator.CreateInstance(someButton.GetType()), Params);
// Also doesn't work...
When I set a watch on the GetEventHandlers MethodInfo, it looks great, it just doesn't like what I'm passing it when I call the Invoke.
I feel like I'm at the very last step of how to get a list of the RoutedEvent Handlers (like good old GetInvocationList(), which doesn't work for WPF RoutedEvents, apparently). From there, it'll be simple enough to remove those Handlers from each control and have an eventless form, which I can then add my own events to.
Any clues? Again, if there's a better/easier way to do the task overall, let me know :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您使用 GetEventHandlers.Invoke(EventHandlersStore , Params) ,它似乎运行良好并且不会崩溃。
If you use
GetEventHandlers.Invoke(EventHandlersStore , Params)
it seems to work well and does not crash.使用上面的代码,我这样做了:
一旦你有了它,那么唯一的问题是你现在有了方法信息,所以你需要获取实现该方法的对象的实例。 通常事件处理程序是在 Window 或 Page 对象上定义的。 所以要得到它:
var 父 = VisualTreeHelper.GetParent(control);
while (!(控件是Window) && !(控件是Page))
{
父 = VisualTreeHelper.GetParent(父);
然后
,使用该实例,您可以使用以下命令调用事件:
Using your code from above I did this:
Once you have that then the only problem is that you now have the method info, so you need to get an instance to the object that implements that method. Usually event handlers are defined on the Window or Page object. So to get it:
var parent = VisualTreeHelper.GetParent(control);
while (!(control is Window) && !(control is Page))
{
parent = VisualTreeHelper.GetParent(parent);
}
And with that instance you can then invoke the event wtih:
如果你采取不同的方法怎么办? 您可以为所有事件调用 EventManager.RegisterClassHandler() ,然后在您的处理程序中(假设该事件是针对设计图面上的控件,而不是 UI 的一部分)将该事件标记为已处理。 这应该可以防止它被转发到设计表面上的控件,因为类处理程序是在标准事件处理程序之前调用的。
您仍然需要使用反射来获取控件提供的事件列表,但至少这样您就不会使用反射来删除事件。 另外,如果您加载的程序集还注册了一个类处理程序(可能在您的代码之前),那么它们的处理程序将首先被调用,但我猜这种情况很少发生。
What if you take a different approach. You could call EventManager.RegisterClassHandler() for all events, and then in your handler (assuming the event is for a control on the design surface, and not part of your UI) mark the event as handled. This should prevent it from being forwarded on to the controls on your design surface, since class handlers are called before standard event handlers.
You'd still need to use reflection to get the list of events provided by a control, but at least this way you wouldn't be using reflection to remove the events. Also, if the assembly you load also registers a class handler (likely before your code does), theirs would be called first, but I would guess that this would be a rare occurrence.