How to Removehandler event? - vb.net

I had difficulty deleting an event
, I only call contextmenu temporarily (assigning the event and after the event has finished I no longer use it), and so on for each call.
sub register()
Dim f_Cm As Windows.Forms.ContextMenuStrip = New System.Windows.Forms.ContextMenuStrip(Me.components)
AddHandler f_Cm.Closed, Sub() f_Cm_Closed(f_Cm)
end sub
'mycode1
Private Sub f_Cm_Closed(f_Cm As Windows.Forms.ContextMenuStrip)
'....mycode
RemoveHandler f_Cm.Closed, Sub() f_Cm_Closed(f_Cm)
End Sub
'mycode2
Private Sub f_Cm_Closed(f_Cm As Windows.Forms.ContextMenuStrip)
'....mycode
Dim e1 As ToolStripDropDownClosedEventArgs = address of f_Cm_Closed(f_Cm)
RemoveHandler f_Cm.Closed, e1
End Sub
Do I need to delete them in this case? and how to do this?
Thanks you!

Sub() f_Cm_Closed(f_Cm) is what's called a lambda expression. Lambda expression are basically methods without a name; they're useful shortcuts in some situations. What you are doing in the code Sub() f_Cm_Closed(f_Cm) is creating a new, nameless method, which then calls f_Cm_Closed(f_Cm).
This isn't what you want, you want to pass a reference directly to your handler so you can remove it later. For that, you use AddressOf.
Before you can do that, the method signatures will have to match. So
Private Sub f_Cm_Closed(f_Cm As Windows.Forms.ContextMenuStrip)
will have to become
Private Sub f_Cm_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs)
sender will always be f_Cm, so you can cast like so:
Dim f_Cm As Windows.Forms.ContextMenuStrip = sender
To pull everything together, your AddHandler call now becomes:
AddHandler f_Cm.Closed, AddressOf f_Cm_Closed
And your method f_Cm_Closed becomes:
Private Sub f_Cm_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs)
Dim f_Cm As Windows.Forms.ContextMenuStrip = sender
RemoveHandler f_Cm.Closed, AddressOf f_Cm_Closed
End Sub
As a final thought, I have no idea why you would want to remove the handler for the Closed event after the menu is closed. But this is how you would restructure your code to do it.

Related

Multiple events with one sub

I have a problem with a code I written in VB.NET that use the AddHandler/RemoveHandler token.
This is the code that I have written:
Private Sub Remove_Handler()
RemoveHandler txtSearch.TextChanged, AddressOf Ricarica_elenco
RemoveHandler cbAttori.SelectedIndexChanged, AddressOf Ricarica_elenco
RemoveHandler cbGeneri.SelectedIndexChanged, AddressOf Ricarica_elenco
RemoveHandler cb_plex.CheckStateChanged, AddressOf Ricarica_elenco
End Sub
Private Sub Add_Handler()
AddHandler txtSearch.TextChanged, AddressOf Ricarica_elenco
AddHandler cbAttori.SelectedIndexChanged, AddressOf Ricarica_elenco
AddHandler cbGeneri.SelectedIndexChanged, AddressOf Ricarica_elenco
AddHandler cb_plex.CheckStateChanged, AddressOf Ricarica_elenco
End Sub
Private Sub Ricarica_elenco(sender As Object, e As EventArgs)
CreaElenco()
End Sub
I have 4 events that handles one subroutine. After executing the "Add_Handler" sub, When two or more indicated events occur simultaneously, the subroutine "Ricarica_Elenco" is executed several times.
I need to create a custom event (or someelse) that collects the 4 original events and executes the procedure "Ricarica_Elenco" only once if two or more events occur simultaneously.
How can I do it?
Thanks
Marcello
Events don't occur simultaneously. Probably inside the CreaElenco method you are doing something that triggers another call to CreaElenco like modifying the text of txtSearch or modifying the current index of cbAttori
This is enough to trigger the call to the associated event handler and in cascade a call to CreaElenco while you are still processing the first call to CreaElenco. A simple solution is to remove the handlers before entering the CreaElenco method and readding them when you have finished. Of course you should be sure to always readd the handlers whatever happens inside the CreaElenco method. This could be accomplished adding a Try/Finally clause around the code that removes the handlers.
Private Sub Ricarica_elenco(sender As Object, e As EventArgs)
Try
Remove_Handler()
CreaElenco()
Finally
Add_Handler()
End Try
End Sub
One way to go about this is to create a static boolean variable which indicate whether the method is running or not:
Private Sub Ricarica_elenco(sender As Object, e As EventArgs)
Static currentlyRunning As Boolean
If currentlyRunning Then Exit Sub
currentlyRunning = True
CreaElenco()
currentlyRunning = False
End Sub
You can also replace the static variable with a class-level variable if you prefer.

Passing Parameter to RemoveHandler VB.NET

I have 3 check boxes that I want to act as a radial button group, but my new job is in vb.net and WinForms.
My hope was to simplify the 3 event handlers by replacing the commented code with a function call like this:
Private Sub cbLinqQuery_CheckedChanged(sender As Object, e As EventArgs) handles cbLinqQuery.CheckedChanged
SafeCheckChg(cbDataTable, cbDataTable_CheckedChanged, cbLinqQuery.Checked)
'RemoveHandler cbDataTable.CheckedChanged, AddressOf cbDataTable_CheckedChanged
'cbDataTable.Checked = Not cbLinqQuery.Checked
'AddHandler cbDataTable.CheckedChanged, AddressOf cbDataTable_CheckedChanged
RemoveHandler cbArray.CheckedChanged, AddressOf cbArray_CheckedChanged
cbArray.Checked = Not cbLinqQuery.Checked
AddHandler cbArray.CheckedChanged, AddressOf cbArray_CheckedChanged
btnDefaultBoundData_Click(sender, e)
End Sub
Private Sub SafeCheckChg(ByRef cb As CheckBox, ByRef handler As EventHandler, checked As Boolean)
RemoveHandler cb.CheckedChanged, AddressOf handler
cb.Checked = Not checked
AddHandler cb.CheckedChanged, AddressOf handler.Clone
End Sub
But I've had no success passing the event handler as a parameter. I've tried passing it as an event handler, a delegate and a void but using it in the RemoveHandler/AddressOf call doesn't seem to be understood by the compiler.
Is there a method where I can get the event handler off of the passed object? Is there a type I can use as the parameter. Obviously I'm not desperate, this is in the name of better code not better functionality.
So Found a pretty close answer that worked with a small tweak:
Inspiration
Solution was simply to pass AddressOf foo as the parameter and remove the AddressOf from the RemoveHandler / AddHandler call. (Which for some reason I thought was a required part of the Add/RemoveHandler call and not a separate function call operating on a parameter.)
Final Code:
Private Sub cbLinqQuery_CheckedChanged(sender As Object, e As EventArgs) Handles cbLinqQuery.CheckedChanged
SafeCheckChg(cbDataTable, AddressOf cbDataTable_CheckedChanged, cbLinqQuery.Checked)
SafeCheckChg(cbArray, AddressOf cbArray_CheckedChanged, cbLinqQuery.Checked)
btnDefaultBoundData_Click(sender, e)
End Sub
Private Sub SafeCheckChg(ByRef cb As CheckBox, ByRef handler As EventHandler, checked As Boolean)
RemoveHandler cb.CheckedChanged, handler
cb.Checked = checked
AddHandler cb.CheckedChanged, AddressOf handler.Clone
End Sub

How do I fire an event in VB.NET code?

I have a form that has a start button (to allow users to run the processes over and over if they wish), and I want to send a btnStart.Click event when the form loads, so that the processes start automatically.
I have the following function for the btnStart.Click event, but how do I actually tell Visual Basic 'Pretend someone has clicked the button and fire this event'?
I've tried going very simple, which essentially works. However, Visual Studio gives me a warning Variable 'sender' is used before it has been assigned a value, so I'm guessing this is not really the way to do it:
Dim sender As Object
btnStart_Click(sender, New EventArgs())
I have also tried using RaiseEvent btnStart.Click, but that gives the following error:
'btnStart' is not an event of 'MyProject.MyFormClass
Code
Imports System.ComponentModel
Partial Public Class frmProgress
Private bw As BackgroundWorker = New BackgroundWorker
Public Sub New()
InitializeComponent()
' Set up the BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
' Fire the 'btnStart.click' event when the form loads
Dim sender As Object
btnStart_Click(sender, New EventArgs())
End Sub
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
If Not bw.IsBusy = True Then
' Enable the 'More >>' button on the form, as there will now be details for users to view
Me.btnMore.Enabled = True
' Update the form control settings so that they correctly formatted when the processing starts
set_form_on_start()
bw.RunWorkerAsync()
End If
End Sub
' Other functions exist here
End Class
You should send a button as sender into the event handler:
btnStart_Click(btnStart, New EventArgs())
Steps in involved in raising an event is as follows,
Public Event ForceManualStep As EventHandler
RaiseEvent ForceManualStep(Me, EventArgs.Empty)
AddHandler ForceManualStep, AddressOf ManualStepCompletion
Private Sub ManualStepCompletion(sender As Object, e As EventArgs)
End Sub
So in your case, it should be as below,
btnStart_Click(btnStart, EventArgs.Empty)
Just Call
btnStart.PerformClick()
You are trying to implement a bad idea. Actually, you have to make a subroutine to accomplish these kind of tasks.
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
call SeparateSubroutine()
End Sub
private sub SeparateSubroutine()
'Your code here.
End Sub
And then whereever you want to call the btnStart's click event, just call that SeparateSubroutine. This should be a correct way in your case.
You can subclass the button and make its OnClick Method public as I described here.

BackGroundWorker RunWorkerCompleted Event Not Called Sometimes

I'm seeing some strange behavior where the RunWorkerCompleted event for one of two threads I start isn't being called depending on how I call them. Check out the code below, and the two methods of firing the threads, good() and bad().
Public Class Form1
Private WithEvents bw As System.ComponentModel.BackgroundWorker
Private WithEvents bw2 As System.ComponentModel.BackgroundWorker
Private starts As Integer = 0
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
starts += 1
Threading.Thread.Sleep(e.Argument)
End Sub
Private Sub bw_Completed(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
MessageBox.Show("Ending " + starts.ToString())
End Sub
Private Sub bad()
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5000)
Threading.Thread.Sleep(500)
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5)
End Sub
Private Sub good()
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(500)
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(5)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'good()
bad()
End Sub
End Class
In both cases the DoWork event is called for both threads. But in the bad() case, only the second thread fires the RunWorkerCompleted event. This is obviously due to the two different ways I'm using VB to handle events here. I'm looking for an explanation of this behavior, preferably with a link to some documentation that could help me understand how these events are being handled in VB better. It seems strange to me that just reusing a variable name here seems to either dispose of the thread before it's done or else just make it stop firing events.
Where is this automatic unsubscribing documented?
In the Visual Basic Language Specification, a document you can download from Microsoft. Chapter 9.6.2 "WithEvents Variables" says this:
The implicit property created by a WithEvents declaration takes care of hooking and unhooking the relevant event handlers. When a value is assigned to the variable, the property first calls the remove method for the event on the instance currently in the variable (unhooking the existing event handler, if any). Next the assignment is made, and the property calls the add method for the event on the new instance in the variable (hooking up the new event handler).
The bolded phrase describes the behavior you see. It is rather important it works that way. If it didn't then you could never unsubscribe from an event and the event subscriptions would pile on without limit.

Create a dynamic control and AddHandle WITH Values/Brackets

it seems that adding for example a button Dim myButton as New Button and then addHandler to mySub("lol", 255) is not possible.
Where mySub is Shared Sub MySub(byRef myString as string, myInteger as Integer)
So: addHandler myButton.click, addressOf mySub("lol", 255) - returns an error saying it does not work with parentheses or whatever.
I somehow see why this might not be possible, so I'm looking for a work-around on this problem.
Please help _jakeCake
First of all the syntax for AddHandler would be:
AddHandler myButton.click, AddressOf mySub
Secondly the signature of the eventhandler procedure must match the signature of the event like so:
Private Sub myButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
[...]
End Sub
Maybe you could look into using a lambda expression when you add the event. When using lambda's in VB.NET the function must return a value and does not support multi-line statements.
Dim myButton As New Button
AddHandler myButton.Click, Function(senderObj, args) myFunc("lol", 255)