2009-06-11 11 views
3

Hintergrund: Ich verwende WPF und C# (3.5) und arbeite an einer App, die es einem Benutzer ermöglicht, ein Formular/Fenster/usercontrol anzuzeigen, das bereits Teil einer kompilierten Assembly ist. Wenn sie es sehen, sollten sie in der Lage sein, auf ein beliebiges Steuerelement (Schaltflächen, Textfelder, sogar Etiketten) zu klicken, ein kleines Popup-Editor sollte vom Steuerelement angezeigt werden, wo sie dann einen Tooltip, helpID usw. für das Steuerelement eingeben können.Entfernen von Routingereignishandlern durch Reflektion?

Das lange und kurze: Ich muss eine grundlegende Entwurfsansicht in WPF nachahmen. Was bedeutet, ich brauche die folgenden zumindest zu tun:

  • Legen Sie das Usercontrol/Fenster aus einer gegebenen Baugruppe (kein Problem)
  • es die Usercontrol/Fenster (kein Problem) Instantiate
  • löschen Sie alle abonnierten Eventhandler für alle seine Kontrollen
  • my own „ShowEditorPopup“ Eventhandler zu jeder Kontrolle vergeben (sollte kein Problem sein)

Zunächst, wenn jemand Vorschläge für eine einfachere oder bessere Route zu nehmen, lass es mich wissen. (Anscheinend gibt es keine DesignHost Art von Komponente für WPF (wie ich .NET 2 gelesen habe), also das ist raus.)

Ich stecke auf dem fettgedruckten Artikel fest - lösche alle abonnierten EventHandler. Nachdem ich mich mit Reflector beschäftigt habe, bin ich auf diesen coolen Brocken gefährlichen Codes gestoßen (hier versuche ich nur, alle EventHandler für einen einzelnen Button namens someButton im XAML definiert zu bekommen):

<Button Name="someButton" Click="someButton_Click"/> 

Hier ist der Code (Sie es von der someButton_Click Eventhandler ausführen können, wenn Sie wollen):

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

es funktioniert auf die Invoke auf, die zurückgibt: Objekt nicht Zieltyp übereinstimmt (dh mein params oder Ziel Objekt sind vermurkst). Ich habe festgestellt, Sie können dieses Problem mit:

GetEventHandlers.Invoke(Activator.CreateInstance(someButton.GetType()), Params); 
// Also doesn't work... 

Wenn ich eine Uhr auf dem GetEventHandlers Method gesetzt, es sieht gut aus, es funktioniert einfach nicht, was ich bin es vorbei, als ich die Invoke rufen.

Ich fühle mich wie im letzten Schritt, wie man eine Liste der RoutedEvent-Handler (wie gute alte GetInvocationList(), die nicht funktioniert für WPF RoutedEvents, anscheinend). Von dort wird es einfach genug sein, diese Handler von jedem Steuerelement zu entfernen und ein ereignisloses Formular zu haben, dem ich dann meine eigenen Ereignisse hinzufügen kann.

Irgendwelche Hinweise? Wenn es einen besseren/einfacheren Weg gibt, die Aufgabe insgesamt zu erledigen, lassen Sie es mich wissen :)

+0

Wow, Sie ein großes Werk war, war ich nur das Gleiche zu tun versuchen. Du hast mir nur den Ausgangspunkt gegeben, den ich brauchte. – orellabac

Antwort

3

Was ist, wenn Sie einen anderen Ansatz wählen? Sie können EventManager.RegisterClassHandler() für alle Ereignisse aufrufen, und dann in Ihrem Handler (vorausgesetzt, das Ereignis ist für ein Steuerelement auf der Entwurfsoberfläche und nicht Teil Ihrer Benutzeroberfläche) markieren Sie das Ereignis als behandelt. Dies sollte verhindern, dass es an die Steuerelemente auf Ihrer Entwurfsoberfläche weitergeleitet wird, da Klassenhandler vor Standardereignishandlern aufgerufen werden.

Sie müssten immer noch reflection verwenden, um die Liste der von einem Steuerelement bereitgestellten Ereignisse abzurufen, aber zumindest würden Sie auf diese Weise keine Reflektion verwenden, um die Ereignisse zu entfernen.Wenn die Assembly, die Sie laden, auch einen Klassenhandler registriert (wahrscheinlich bevor Ihr Code dies tut), würden sie zuerst aufgerufen, aber ich würde vermuten, dass dies ein seltenes Ereignis wäre.

+0

Großer Vorschlag. Ich habe mich gefragt, ob es möglich ist, andere Ereignisse durch das Setzen von Handled = true zu blockieren, aber ich wusste nicht, dass "Class Handlers" zuerst ausgelöst werden. Ich werde es versuchen. Vielen Dank! – Eddie

+0

Reines Genie! Nachdem ich http://www.switchonthecode.com/tutorials/wpf-snippet-class-event-handlers für ein verwandtes Beispiel angeschaut habe, mache meine Schaltfläche nichts mehr. Genau das, was ich brauchte. Vielen Dank!! Jetzt frage ich mich, wie viele RoutedEvents ich neben einem einfachen Button.ClickEvent blockieren sollte ... vielleicht wäre die sicherste Sache, alle RoutedEvents, die von EventManager.GetRoutedEvents() zurückgegeben werden, "vor zu behandeln". Aber das ist ein anderes Problem :) Danke nochmal! – Eddie

+0

Und die speziellen Fälle, die Sie erwähnt haben, wo es möglicherweise Probleme geben könnte, sollten in unserem Fall kein Problem sein. Was du mir gegeben hast, passt gut zur Situation. – Eddie

3

Wenn Sie verwenden, scheint es gut zu funktionieren und stürzt nicht ab.

0

Verwenden Sie den Code von oben ich dies tat:

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

Sobald Sie, dass dann das einzige Problem ist, dass Sie jetzt die Methode info, so dass Sie eine Instanz auf das Objekt erhalten müssen, die die implementiert Methode. Normalerweise sind Ereignishandler auf dem Window- oder Page-Objekt definiert. Um es zu bekommen: var parent = VisualTreeHelper.GetParent (Kontrolle); while (! (Kontrolle ist Fenster) & &! (Kontrolle ist Seite)) { parent = VisualTreeHelper.GetParent (Eltern); }

Und mit diesem Beispiel können Sie dann das Ereignis Wtih aufrufen:

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