שתף באמצעות


AddHandler, how to know if a handler already exists?

Question

Wednesday, October 28, 2009 4:44 PM

When adding a handler I want to be sure adding a unique(single) instance of the delegate. How can I check it?

Concrete problem:

I have the following code in a custom FlowLayoutPanel:

  Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlAdded(e)
    AddHandler e.Control.MouseMove, AddressOf Control_MouseMove
    AddHandler e.Control.MouseDown, AddressOf Control_MouseDown
    AddHandler e.Control.MouseUp, AddressOf Control_MouseUp
  End Sub

  Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlRemoved(e)
    'RemoveHandler e.Control.MouseMove, AddressOf Control_MouseMove
    'RemoveHandler e.Control.MouseDown, AddressOf Control_MouseDown
    'RemoveHandler e.Control.MouseUp, AddressOf Control_MouseUp
  End Sub

I remove my control from the myFlowLayoutPanel, cause it's impossible to move a contrrol in runtime inside it.

So, I commented the RemoveHandler, cause I need to keep the mouseMoving on the control even if the control was removed from myPanel.

Best regards, Sergiu

All replies (18)

Wednesday, October 28, 2009 6:13 PM ✅Answered | 5 votes

The best practice is to always first remove, then add handlers from the event.  This guarantees no duplicates.

RemoveHandler e.Control.MouseMove, _mouseMoveHandler
AddHandler e.Control.MouseMove, _mouseMoveHandler

Any attempts to remove an event handler that is not in the Invocation List at the time of the attempt does not throw an exception.

Rudey  =8^D

Mark the best replies as answers. "Fooling computers since 1971."


Wednesday, October 28, 2009 5:07 PM

This where VB and C# diverge. 
VB doesn't allow you to look at events like that.  Use delegates in VB

    Delegate Sub MyDelegate()
    Sub TestDelegate()
        Dim d1 As MyDelegate
        d1 = New MyDelegate(AddressOf JobTask1) 'AddHandler
        Dim d2 As MyDelegate
        d2 = New MyDelegate(AddressOf JobTask1) 'AddHandler
        Dim d As MyDelegate
        d = MyDelegate.Combine(d1, d2) 'AddHandler
        Dim delegates() As [Delegate] = d.GetInvocationList()
    End Sub

But, be aware that the fact that VB restricts access to event delegates doesn't mean that VB is inferior.  In most circumstances, there should be no need to examine the Invocation List.  Besides, the order of execution isn't guaranteed by the CLR.  Sounds like good reason to leave it out of the language to me.

Rudy   =8^DMark the best replies as answers. "Fooling computers since 1971."


Wednesday, October 28, 2009 5:22 PM

Sergiu,

Your code uses the words from VB but therefore it is not VB,  did you use a kind of decoder from a language which misses a lot of the features from VB?

Success
Cor


Wednesday, October 28, 2009 5:36 PM

Sergiu,

Your code uses the words from VB but therefore it is not VB,  did you use a kind of decoder from a language which misses a lot of the features from VB?


Success
Cor

no, no, I wrote this code by myself... I am a C# developer, and I don't know very good VB.NET (I don't like VB.NET, but forced for a project:)

Rudy, thanks, but could you be more explicit for my case? I mean... Why need I to add delegates...

I wrote this test code and it works fine:

Public Class MyFlowLayoutPanel
  Inherits FlowLayoutPanel
  Private _controlTemporaryRemoved As Boolean

  Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlAdded(e)
    If Not _controlTemporaryRemoved Then
      AddHandler e.Control.MouseMove, AddressOf Control_MouseMove
      AddHandler e.Control.MouseDown, AddressOf Control_MouseDown
      AddHandler e.Control.MouseUp, AddressOf Control_MouseUp
    End If
  End Sub

  Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlRemoved(e)
    If Not _controlTemporaryRemoved Then
      RemoveHandler e.Control.MouseMove, AddressOf Control_MouseMove
      RemoveHandler e.Control.MouseDown, AddressOf Control_MouseDown
      RemoveHandler e.Control.MouseUp, AddressOf Control_MouseUp
    End If
  End Sub

  Private Sub Control_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim myForm As Form = Me.FindForm()
    Dim ctl As Control = CType(sender, Control)
    _controlTemporaryRemoved = True

    myForm.Controls.Add(ctl)
    ctl.BringToFront()
  End Sub

  Private Sub Control_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim ctl As Control = CType(sender, Control)

    Dim currentPoint As Point = ctl.Location
    Dim backCtl As Control = Me.GetChildAtPoint(currentPoint, GetChildAtPointSkip.None)

    Me.Controls.Add(ctl)
    _controlTemporaryRemoved = False

    If backCtl IsNot Nothing Then
      Dim index As Integer = Me.Controls.GetChildIndex(backCtl)
      Me.Controls.SetChildIndex(ctl, index + 1)
    ElseIf currentPoint.Y < 0 Then
      Me.Controls.SetChildIndex(ctl, 0)
    End If
  End Sub

  Private Sub Control_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim ctl As Control = CType(sender, Control)
    If e.Button = Windows.Forms.MouseButtons.Left Then
      ctl.Location = ctl.Location + e.Location - New Point(ctl.Width \ 2, ctl.Height \ 2)
    End If
  End Sub
End Class

Best regards, Sergiu


Wednesday, October 28, 2009 5:41 PM

NB.

When the form adds the control this control is automatically removed from the FlowLayoutPanel collection, and viceversa.

Maybe this is not very correct of point of view of VB.NEt programming, but this works as I want... however, it's a pity that I cand test if the handler for the Move function, by e.g. was already added to this control.

in fact, I try to reproduce the designer behavior liek described in the image of this topic


Best regards, Sergiu


Wednesday, October 28, 2009 5:44 PM

Why use a delegate?  I thought I had explained it already.  Nahp, I didn't do it very well.

The Invocation List property of a delegate lists all of the current subscribers/targets for the delegate.

Unlike C#, VB doesn't allow you to reference event members of a class.
But events are really wrappers for delegates anyway.

I point out again, the order of execution on an event's Invocation List is not guaranteeed.
If your code depends upon the order, then do not use an event.  Use a delegate, instead.
I think the best solution is to re-think your design. 
The fact that VB restricts access to an event's IL speaks volumes.  Don't do it.

In order to use a delegate instead of an event, I had provided a code sample that shows you to add event handlers, and combine delegates to get a single delegate with multiple targets in its' Invocation List.

Rudedog  =8^D


Mark the best replies as answers. "Fooling computers since 1971."


Wednesday, October 28, 2009 6:00 PM

So, the solution should be this one:

  Private _mouseUpHandler As MouseEventHandler
  Private _mouseDownHandler As MouseEventHandler
  Private _mouseMoveHandler As MouseEventHandler

  Public Sub New()
    _mouseUpHandler = New MouseEventHandler(AddressOf Control_MouseUp)
    _mouseDownHandler = New MouseEventHandler(AddressOf Control_MouseDown)
    _mouseMoveHandler = New MouseEventHandler(AddressOf Control_MouseMove)
  End Sub

  Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlAdded(e)
    e.Control.Margin = New Padding(0)
    'If Not _controlTemporaryRemoved Then
    AddHandler e.Control.MouseMove, _mouseMoveHandler
    AddHandler e.Control.MouseDown, _mouseDownHandler
    AddHandler e.Control.MouseUp, _mouseUpHandler
    'End If
  End Sub

  Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlRemoved(e)
    'If Not _controlTemporaryRemoved Then
    'RemoveHandler e.Control.MouseMove, _mouseMoveHandler
    'RemoveHandler e.Control.MouseDown, _mouseDownHandler
    'RemoveHandler e.Control.MouseUp, _mouseUpHandler
    'End If
  End Sub

....

however duplicate handlers are added.

I don't need to combine anything, like in your example, I need to check if the delegate was nor not already added.... I need to add only 3 delegates to a control, not more or less.


Best regards, Sergiu


Wednesday, October 28, 2009 6:39 PM | 1 vote

Have you looked at custom event accesors in VB.NET?  Basically, the equivalent of add/remove in C#.  You can do something like:

Option Strict On
Option Explicit On
Option Infer Off

Module Module1


    Sub Main()
        Dim c As New ClassWithACustomEvent
        AddHandler c.TestEvent, AddressOf TestEvent
        AddHandler c.TestEvent, AddressOf TestEvent

        c.DoCoolStuff()

        Console.ReadKey()

        RemoveHandler c.TestEvent, AddressOf TestEvent
        RemoveHandler c.TestEvent, AddressOf TestEvent

    End Sub

    Class ClassWithACustomEvent
        Private _eventHandlers As New List(Of EventHandler)

        Public Custom Event TestEvent As EventHandler
            AddHandler(ByVal value As EventHandler)
                If Not _eventHandlers.Contains(value) Then
                    Console.WriteLine("adding")
                    _eventHandlers.Add(value)
                End If
            End AddHandler

            RemoveHandler(ByVal value As EventHandler)
                Console.WriteLine("removing: {0}", _eventHandlers.Remove(value))
            End RemoveHandler

            RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
                For Each handler As EventHandler In _eventHandlers
                    handler.Invoke(sender, e)
                Next
            End RaiseEvent
        End Event

        Public Sub DoCoolStuff()
            RaiseEvent TestEvent(Me, EventArgs.Empty)
        End Sub
    End Class
    
    Private Sub TestEvent(ByVal sender As Object, ByVal e As System.EventArgs)
        Console.WriteLine("Executing")
    End Sub
End Module

As you can see, the event is only added to the internal collection one time.  This is a rarely used and little known feature of VB added with 2005.  C# has had similar from the begining - but, it's almost never used :)

Tom Shelton


Wednesday, October 28, 2009 6:49 PM

That's true.

Just keep in mind that the order of execution of the targets in the Invocation List is not guaranteed at all.
Mark the best replies as answers. "Fooling computers since 1971."


Thursday, October 29, 2009 9:11 AM

the event is only added to the internal collection one time. 


Tom Shelton

I am not sure about it. If this where true, I'd never posted this post :)

If I add multiple times Add the same handler my control moves a little "in a crazy way", I see that it recalculates and sets multiple times the control location, and the control black border "paints" all the panel with black lines.

Best regards, Sergiu


Thursday, October 29, 2009 3:02 PM

I can't account for your behavior - I'm not seeing any code.  But, if you run the code I posted you will see that the event handler is only added once.Tom Shelton


Thursday, October 29, 2009 3:07 PM

I can't account for your behavior - I'm not seeing any code.  But, if you run the code I posted you will see that the event handler is only added once.


Tom Shelton

Sub Main()
        Dim c As New ClassWithACustomEvent
        AddHandler c.TestEvent, AddressOf TestEvent
        AddHandler c.TestEvent, AddressOf TestEvent

        c.DoCoolStuff()

        Console.ReadKey()

        RemoveHandler c.TestEvent, AddressOf TestEvent
        RemoveHandler c.TestEvent, AddressOf TestEvent

    End Sub

Looks like it gets added twice. 
Sergiu, one of those AddHandler lines just needs to be commented out or completely removed.Mark the best replies as answers. "Fooling computers since 1971."


Thursday, October 29, 2009 3:53 PM

I can't account for your behavior - I'm not seeing any code.  But, if you run the code I posted you will see that the event handler is only added once.


Tom Shelton

Tom, I didn't test your code, but I believe(it's evident) that your code adds the handler only once .(If Not _eventHandlers.Contains(value) Then ...)

(a propos, how do you can see any code, when I posted above my all inherited control test code?.., but this does not matter)

Now, I talk about .NET Framework objects (Control, in my case), that adds the same handler multiple times.
I can't see(have no time) the code of .NET "inside", but I have one behavior of the control if I add only once the handler, and a different behavior when I add it multiple times.


Best regards, Sergiu


Thursday, October 29, 2009 4:17 PM | 1 vote

Hi Sergiu,

FWIW, despite the fact that you probably do not need this due to the explanations in the other posts, here's a little helper-class and method that will return the count of an event's subscribers:

Option Explicit On
Option Strict On

Public Class SubscriberHelper

   ''' <summary>
   ''' Returns the count of subscribers to the specified Event (i.e. "Click") for the given control/object
   ''' </summary>
   ''' <param name="eventSource">Reference to the control/object</param>
   ''' <param name="eventName">Name of the event (i.e. "Click")</param>
   ''' <returns>The number of subscribers to the named event.</returns>
   Public Shared Function GetSubscriberCount(ByVal eventSource As Object, ByVal eventName As String) As Integer
      Dim strName As String = "Event" + eventName
      Dim intSubscriberCount As Integer = 0
      Dim targetType As Type = eventSource.[GetType]()

      Do
         Dim fields As System.Reflection.FieldInfo() = targetType.GetFields(System.Reflection.BindingFlags.[Static] _
            Or System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)

         For Each field As System.Reflection.FieldInfo In fields
            If field.Name = strName Then
               Dim eventHandlers As System.ComponentModel.EventHandlerList = _
               (DirectCast((eventSource.[GetType]().GetProperty("Events", _
               (System.Reflection.BindingFlags.FlattenHierarchy Or _
               (System.Reflection.BindingFlags.NonPublic Or _
                System.Reflection.BindingFlags.Instance))).GetValue(eventSource, Nothing)),  _
               System.ComponentModel.EventHandlerList))

               Dim d As [Delegate] = eventHandlers(field.GetValue(eventSource))

               If (Not (d = Nothing)) Then
                  Dim subscribers As [Delegate]() = d.GetInvocationList()

                  For Each d1 As [Delegate] In subscribers
                     System.Math.Max(System.Threading.Interlocked.Increment(intSubscriberCount), intSubscriberCount - 1)
                  Next

                  Return intSubscriberCount
               End If
            End If
         Next

         targetType = targetType.BaseType
      Loop While targetType IsNot Nothing

      Return intSubscriberCount
   End Function

End Class

If i.e. you want to know the number of subscribers to a the Click-event of a button named "cmdMyButton", you'd call this like ...

dim intCount as integer = SubscriberHelper.GetSubscriberCount(cmdMyButton, "Click")

As a result, a <dim fAlreadySubscribed=(intCount<1) will give you what you originally asked for.

For the records: this is the modified version of a snippet that I found somewhere on the web. My bad that I didn't make a note of the original author; it wasn't me, however. :-P

Cheers,
Olaf


Thursday, October 29, 2009 4:34 PM

Thank you, Olaf.

Your utility just confirmed my suppositions about adding multiple handlers:

Tested on

  Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlAdded(e)
    e.Control.Margin = New Padding(0)
    'If Not _controlTemporaryRemoved Then
    '''''''RemoveHandler e.Control.MouseMove, _mouseMoveHandler
    RemoveHandler e.Control.MouseDown, _mouseDownHandler
    RemoveHandler e.Control.MouseUp, _mouseUpHandler

    AddHandler e.Control.MouseMove, _mouseMoveHandler
    AddHandler e.Control.MouseDown, _mouseDownHandler
    AddHandler e.Control.MouseUp, _mouseUpHandler

    '''''''''''' TEST NUMBER HANDLERS
    Dim intCount As Integer = SubscriberHelper.GetSubscriberCount(e.Control, "MouseMove")
    'End If
  End Sub

at the end of the method I set the breackpoint that outputs:
"{e.Control.Text}" has {intCount} MouseMoves; Function: $FUNCTION, Thread: $TID $TNAME

This is the result after "moving" my controls in runtime:

""Label2"" has 1 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label4"" has 1 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label5"" has 1 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label2"" has 1 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label4"" has 2 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label3"" has 2 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label3"" has 3 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label5"" has 2 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label5"" has 3 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label2"" has 2 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label2"" has 3 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>
""Label2"" has 4 MouseMoves; Function: WindowsApplication1.MyFlowLayoutPanel.OnControlAdded(System.Windows.Forms.ControlEventArgs), Thread: 0xFBC <No Name>

Best regards, Sergiu


Thursday, October 29, 2009 4:37 PM

BUT, surely, I would not use this check for testing my handlers, because of performance reasons. I just remove initially the handler and then will add it, like Rudy suggested.
Best regards, Sergiu


Wednesday, January 26, 2011 6:10 AM

Hi sergiu,

        This is the Key solution 

EventDescriptor e = TypeDescriptor.GetEvents(yourObject).Find("yourEventName", true); 

        Please review following thread for a detailed answer of your case. Check Olaf Rabbachin Solution. http://social.msdn.microsoft.com/Forums/en/vbgeneral/thread/e622390f-246e-467a-b3ea-70eb516f1a4e

I hope this will help you ,,,

 

 Wa'el   .Net Professional


Wednesday, January 26, 2011 8:18 AM

Hi Wa'el,

um, would it be possible that you wanted to post your reply to a different thread ..?

 

Cheers,
Olaf
http://blogs.intuidev.com