הערה
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות להיכנס או לשנות מדריכי כתובות.
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות לשנות מדריכי כתובות.
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