Question

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

Was it helpful?

Solution

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.

OTHER TIPS

If you use GetEventHandlers.Invoke(EventHandlersStore , Params) it seems to work well and does not crash.

Using your code from above I did this:

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

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:

res.[0].Handler.Method.Invoke(parent, new object[] { control, new RoutedEventArgs() }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top