1

i want to Remove a specific handler from a WinForms Control with VisualBasic .Net.

For example:

I have a Windows.Forms.ToolStripButton with a Click-Event registered to save Data.

Now i need to remove this Event from the Button, but I have no access to the registerd Event-Method !! I pnly know which Handle is used. In my case "Click"

For better understanding: I have to write an extension for a GridView. When multiple lines are selected, i have to integrate mass-editing for the selected Rows. For that i need to register a new click event for the savebutton and remove the old one. But i have no acces to the Address of the event handler. I only have the button with the registered Event

This is what i tried so far:

Dim btnSaveEventInfo = btnSave.GetType.GetEvent("Click")
Dim method = Activator.CreateInstance(btnSaveEventInfo.GetType, True)

RemoveHandler btnSave.Click, method

This throws InvalidCastException

Anyone any idea?

Gr33tz gangfish

gangfish
  • 138
  • 16
  • How was that method registered as event handler in the first place? – Crono Mar 24 '14 at 12:08
  • 1
    possible duplicate of [Is it possible to "steal" an event handler from one control and give it to another?](http://stackoverflow.com/questions/293007/is-it-possible-to-steal-an-event-handler-from-one-control-and-give-it-to-anoth) – Hans Passant Mar 24 '14 at 12:16
  • Methdo was registered with a Handler function: Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click SaveChanges() End Sub – gangfish Mar 24 '14 at 12:20
  • Have you tried to cast `method` to an `EventHandler` explicitly? – MrPaulch Mar 24 '14 at 12:32
  • explicit cast throws same error as written above – gangfish Mar 24 '14 at 12:38

1 Answers1

2

Wow. That was a much harder nut to crack than I expected. It is possible to do this, but I'll warn you, it's a real mess. Ultimately, what you need to do is to get the Delegate object for the event. Once you have that, removing the event handler is easy because you can just do something like this:

Dim d As [Delegate] = ' ... (we'll figure this out later)
For Each handler As [Delegate] In d.GetInvocationList()
    RemoveHandler btnSave.Click, DirectCast(handler, EventHandler)
Next

However, getting the delegate for the event is particularly difficult. Reflection makes it easy to get an EventInfo object for any desired event, but it does not provide a simple way to get the delegate for that event. Technically speaking, there is no way, for sure, to always get the delegate, since events encapsulate their delegate privately and they could be implemented in any custom way. But, since most events are implemented in the "normal" way, it is possible with reflection to get the private delegate hidden in the object. Of course, all of that will break if Microsoft ever decides to change the "normal" way in which it compiles events, but since that's the best option you've got, you're stuck with it.

To further complicate things, the event handlers for WinForm controls are implemented in a custom way (albeit, all consistently with each other), so to get the delegate for a control's event is even more complex. There is an excellent write-up on all of this on this page of Bob Powell's blog. It's too lengthy for me to repeat all of the information here, but I have used it to put together a VB.NET example of how to solve your particular situation:

Dim currType As Type = btnSave.GetType()
Dim eventFieldInfo As FieldInfo = Nothing
Do
    eventFieldInfo = currType.GetField("EventClick", BindingFlags.Static Or BindingFlags.Instance Or BindingFlags.NonPublic)
    If eventFieldInfo Is Nothing Then
        currType = currType.BaseType
    End If
Loop While eventFieldInfo Is Nothing

Dim ehl As EventHandlerList = DirectCast(btnSave.GetType().GetProperty("Events", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy).GetValue(btnSave), EventHandlerList)
Dim d As [Delegate] = ehl(eventFieldInfo.GetValue(btnSave))

For Each handler As EventHandler In d.GetInvocationList()
    RemoveHandler btnSave.Click, handler
Next

See the link above for the explanation of why and how that works. However, as I said, this is quite ugly and could break in future versions of .NET. Therefore, if there is any other way for you to do this, it would be better to use those alternatives. For instance, if you have control over the code that adds the event handler, you could just keep a separate reference to that delegate so you could remove it later, for instance:

Dim myClickHandler As EventHandler = AddressOf btnSave_Click
AddHandler btnSave.Click, myClickHandler

' ...

RemoveHandler btnSave.Click, myClickHandler
Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 1
    Got as far as the EventHandlerList, when I saw that you already formulated a post :) +1 – MrPaulch Mar 24 '14 at 13:37
  • @MrPaulch Nice work! I'm glad to know that I wasn't the only one who got sucked into this rabbit hole. – Steven Doggart Mar 24 '14 at 13:39
  • After testig the above code, it does remove alle click handlers from the code, but only permantly if the Handler was added with: AddHandler btnSaveClick, clickHandler In my case the handler is added to the method and the control is WithEvents. For this the Handler is added permanently, so the above code wont work. Also see: http://stackoverflow.com/questions/2208775/withevents-handles-better-than-remove-addhandler – gangfish Mar 24 '14 at 16:51
  • @gangfish Hmmm. Yeah, that's a tough one. I can't think of any way, even with reflection, that you could remove that `WithEvents` reference. In that case, I'd really stress, even more forcefully, the need to rethink you design :( – Steven Doggart Mar 24 '14 at 17:10
  • @gangfish Or, if you can't change the code that handles the event, perhaps you could come up with some way to wrap the button in another class, or use a derived button class which allows you to stop the event from firing in the first place. – Steven Doggart Mar 24 '14 at 17:12