none
AddHandler, how to know if a handler already exists? RRS feed

  • Question

  • 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
    Wednesday, October 28, 2009 4:44 PM

Answers

  • 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."
    • Marked as answer by Sergiu Dudnic Thursday, October 29, 2009 9:12 AM
    Wednesday, October 28, 2009 6:13 PM

All replies



  • 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^D
    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, October 28, 2009 5:07 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: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

    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:36 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:41 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 5:44 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:00 PM
  • 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."
    • Marked as answer by Sergiu Dudnic Thursday, October 29, 2009 9:12 AM
    Wednesday, October 28, 2009 6:13 PM
  • 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:39 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."
    Wednesday, October 28, 2009 6:49 PM
  • 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 9:11 AM
  • 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: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


    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: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
    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 3:53 PM
  • 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
    • Proposed as answer by saket13 Friday, October 29, 2010 5:40 AM
    Thursday, October 29, 2009 4:17 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:34 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
    Thursday, October 29, 2009 4:37 PM
  • 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

    • Proposed as answer by Wa'el Wednesday, January 26, 2011 6:14 AM
    Wednesday, January 26, 2011 6:10 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
    Wednesday, January 26, 2011 8:18 AM