通过反射移除路由事件处理程序?

5
背景:我正在使用WPF和C#(3.5)开发一个应用程序,允许用户查看已编译程序集中的表单/窗口/用户控件。当他们查看它时,他们应该能够点击任何控件(按钮、文本框,甚至标签),控件旁边会出现一个小弹出式编辑器,在那里他们可以输入提示、帮助ID等内容。
长话短说:我需要模仿WPF中的基本设计视图。这意味着我至少需要执行以下操作:
- 从给定程序集加载用户控件/窗口(没问题) - 实例化用户控件/窗口(没问题) - 清除所有控件的已订阅事件处理程序 - 为每个控件分配自己的“ShowEditorPopup”事件处理程序(不应该是问题)
首先,如果有人有更简单或更好的路线,请告诉我。(显然,WPF没有像.NET 2中那样的DesignHost组件,所以不能用。)
我卡在了粗体字的项目上——清除任何已订阅的事件处理程序。经过一番查找并进入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处理程序列表的最后一步(就像旧的GetInvocationList()那样,显然对于WPF RoutedEvents不起作用)。从那里开始,将很容易从每个控件中删除这些处理程序,并拥有无事件的表单,然后我可以添加自己的事件。
有什么线索吗?同样,如果有更好/更容易完成任务的方法,请告诉我 :)

哇,你做得太棒了!我正试图做同样的事情。你给了我一个我需要的起点。 - orellabac
3个回答

3
如果您采取不同的方法,您可以为所有事件调用EventManager.RegisterClassHandler(),然后在处理程序中(假设该事件是针对设计表面上的控件而不是您的UI的一部分),将事件标记为已处理。这样应该可以防止它被转发到设计表面上的控件,因为类处理程序在标准事件处理程序之前被调用。
您仍然需要使用反射来获取控件提供的事件列表,但至少这样您就不会使用反射来删除事件。此外,如果您加载的程序集也注册了一个类处理程序(很可能是在您的代码之前),则它们将首先被调用,但我认为这种情况很少见。

好建议。我在想是否可以通过设置Handled = true来阻止其他事件的触发,但我不知道“类处理程序”会首先被触发。我会尝试一下。谢谢! - Eddie
纯天才!在查看了http://www.switchonthecode.com/tutorials/wpf-snippet-class-event-handlers的相关示例后,我的按钮不再做任何事情了。正是我需要的。谢谢!现在我想知道除了简单的Button.ClickEvent之外,我应该阻止多少个RoutedEvents...也许最安全的方法是“预处理”从EventManager.GetRoutedEvents()返回的所有RoutedEvents。但这是另一个问题 :) 再次感谢! - Eddie
您提到的特殊情况,在我们的情况下不应该成为问题。您给我提供的内容非常适合目前的情况。 - Eddie
我有一个类似的问题。 我想要清除所有与TextBoxBase.TextChanged事件相关的私有处理程序(即指.NET中的)。 - Shimmy Weitzhandler

3
如果你使用 GetEventHandlers.Invoke(EventHandlersStore , Params),它似乎可以正常工作而且不会崩溃。

1
使用您上面的代码,我做了这个:

// Get the control's Type
Type controlViewType = ((UIElement)control).GetType();

// Dig out the undocumented (yes, I know, it's risky) EventHandlerStore
// from the control's Type
PropertyInfo EventHandlersStoreType =
controlViewType.GetProperty("EventHandlersStore",
BindingFlags.Instance | BindingFlags.NonPublic);

// Get the actual "value" of the store, not just the reflected PropertyInfo
Object EventHandlersStore = EventHandlersStoreType.GetValue(tree, null);
var miGetRoutedEventHandlers 
EventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", 
BindingFlags.Public | BindingFlags.Instance);
RoutedEventHandlerInfo[] res =   
(RoutedEventHandlerInfo[])miGetRoutedEventHandlers.Invoke(EventHandlersStore, 
new object[] { CheckedTreeViewItem.CheckedEvent });

一旦你拥有了方法信息,唯一的问题就是需要获取实现该方法的对象的实例。通常事件处理程序定义在Window或Page对象上。因此,要获取它:

var parent = VisualTreeHelper.GetParent(control);
while (!(control is Window) && !(control is Page))
{
       parent = VisualTreeHelper.GetParent(parent);
}

有了该实例,您可以使用以下代码调用事件:

res.[0].Handler.Method.Invoke(parent, new object[] { control, new RoutedEventArgs() }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接