Removing and adding event handlers for different ComboBoxes where the ComboBox is passed as a parameter - vb.net

I have a method that takes a ComboBox as a parameter and then adds data to it. When data is added, the SelectedIndexChangedEvent fires. Is there a way that, in the called method, I can remove the above event handler for whatever ComboBox is passed as a parameter and then add is at the end of the method? I know how to remove and add specific handlers, but can't figure out how to do it based on which ComboBox is passed.
Here's the method..
Private Sub PopulateComboBox(ByRef cboBox As ComboBox, ByVal itemSource As String)
'Remove handler for cboBox
'Do stuff that would otherwise cause the event handler to execute
'Add handler for cboBox
End Sub
I have 4 ComboBoxes - would it just be easier to remove all 4 event handlers and then add them again at the end of the code? I would however like to know if this is possible so that I can possibly apply to re-usable code in the future

The most basic way to go about this is to do this:
Private Sub PopulateComboBox(ByRef cboBox As ComboBox, ByVal itemSource As String)
RemoveHandler cboBox.SelectedIndexChanged, AddressOf ComboBox1_SelectedIndexChanged
'Do stuff that would otherwise cause the event handler to execute
AddHandler cboBox.SelectedIndexChanged, AddressOf ComboBox1_SelectedIndexChanged
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
End Sub
Another option, which might be better in some circumstances, is to do this:
Private _ignoreComboBox As ComboBox = Nothing
Private Sub PopulateComboBox(ByRef cboBox As ComboBox, ByVal itemSource As String)
_ignoreComboBox = cboBox
'Do stuff that would otherwise cause the event handler to execute
_ignoreComboBox = Nothing
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
If sender Is Not _ignoreComboBox Then
End If
End Sub
Or, to handle multiple combo boxes at the same time:
Private _ignoreComboBoxes As List(Of ComboBox) = New List(Of ComboBox)()
Private Sub PopulateComboBox(ByRef cboBox As ComboBox, ByVal itemSource As String)
_ignoreComboBoxes.Add(cboBox)
'Do stuff that would otherwise cause the event handler to execute
_ignoreComboBoxes.Remove(cboBox)
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
If Not _ignoreComboBoxes.Contains(DirectCast(sender, ComboBox)) Then
End If
End Sub

Here is one way:
' these happen to map to the same event handler
Private cb1Event As EventHandler = AddressOf cbx_SelectedIndexChanged
Private cb2Event As EventHandler = AddressOf cbx_SelectedIndexChanged
Then when used:
PopulateComboBox(cb1, items, cb1Event)
PopulateComboBox(cb2, items, cb2Event)
' or
PopulateComboBox(cb3, items, AddressOf cbx_SelectedIndexChanged)
The method would be declared:
Private Sub PopulateComboBox(cboBox As ComboBox,
items As String, ev As EventHandler)
Personally, since you know the cbo involved anyway, I'd do that before the call:
RemoveHandler cb1.SelectedIndexChanged, AddressOf cbx_SelectedIndexChanged
PopulateComboBox(cb1, items)
AddHandler cb1.SelectedIndexChanged, AddressOf cbx_SelectedIndexChanged
There is not much to be gained by passing all the info to do something to something else so it can do what you know needs to be done.

Related

VB.NET - How to add a large amount of events to a single handle?

Recently I've been working on a program that has a few TextBoxes, CheckBoxes, ComboBoxes, etc., and I found that making one function handle multiple events is pretty simple, you just separate the events with a comma and the code recognizes the inidvidual events.
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click, Button2.Click
MsgBox("Hello World!")
End Sub
However, when you start to have a large number of events that you want handled by the same function, it gets a bit messy.
Private Sub Checks_CheckedChanged(sender As Object, e As EventArgs) Handles chkInput1.CheckedChanged, chkInput2.CheckedChanged, chkInput3.CheckedChanged, chkInput4.CheckedChanged, checkInput5.CheckedChanged, chkOutput.CheckedChanged
MsgBox("Checks Changed!")
End Sub
You can use the line continuation character _ to make it look a little better.
Private Sub Checks_CheckedChanged(sender As Object, e As EventArgs) Handles _
chkInput1.CheckedChanged, chkInput2.CheckedChanged, chkInput3.CheckedChanged, _
chkInput4.CheckedChanged, checkInput5.CheckedChanged, chkOutput.CheckedChanged
MsgBox("Checks Changed!")
End Sub
But you still end up with a nasty block of text. Is there a more clean/concise way of doing this? What I have in mind is that it would be really nice to give an array of object events as an argument but I don't think that's possible.
You could do this by using the
AddHandler ObjectName.EventName, AddressOf EventHandlerName
syntax
It's simple enough to write a Sub that takes an array of object and loops over them to add the handler for each event.
For checkboxes:
Public Sub AddHandlerSub(PassedArray As CheckBox())
For Each item As CheckBox in PassedArray
AddHandler Item.CheckedChanged, AddressOf EventHandlerName
next
End Sub
You can simply iterate the controls in the controls collection and not fuss with an array at all. You can also do further conditions if you want to exclude/add any given control, such as in the example of the TextBox Case in the following example.
Private Sub DataTables_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each Ctrl As Control In Panel1.Controls
Select Case Ctrl.GetType
Case GetType(CheckBox)
AddHandler DirectCast(Ctrl, CheckBox).CheckedChanged, Sub(S As Object, EA As EventArgs)
Dim ChkBox As CheckBox = DirectCast(S, CheckBox)
'do something with ChkBox
End Sub
Case GetType(TextBox)
Select Case Ctrl.Name
Case "TextBox1", "TextBox2" 'Add handle only to these contrls
'Or you could add Case Else and put the below handle within it
'Then this becomes an exclusion case
AddHandler DirectCast(Ctrl, TextBox).TextChanged, Sub(S As Object, EA As EventArgs)
Dim TxtBox As TextBox = DirectCast(S, TextBox)
'do something with TxtBox
End Sub
End Select
End Select
Next
End Sub
Additional Information: You can select procedures as Event Handlers by selecting the control. Then in the Properties window click the lightning bolt to display Events. Selected the event you wish to assign a handler and then the drop down arrow to the right. The resulting list will display all the Subs that match the signature of that event. Select the one you want and the designer will write or append the control to the Handles clause.
Add a procedure to the Form with a signature that matches the event.
Private Sub MultipleButtons(sender As Object, e As EventArgs)
End Sub
In the dropdown the list contains all Subs that match the signature of the event.
The designer writes the Handles clause
Private Sub MultipleButtons(sender As Object, e As EventArgs) Handles Button5.Click
End Sub

AddHandler to ToolStripMenuItem through ContextMenuStrip.Opening event not working

Hi to you all and Merry Christmas.
I have recently inherited a VB projects that I must add functionality. So I have the code below:
Private Sub AddItems()
Dim itemMenu = DirectCast(ContextMenuStrip.Items.Find("name", False)(0), ToolStripMenuItem)
For Each dbObject In dbObjects
Dim item As New ToolStripMenuItem(dbObject.Name)
item.Tag = dbObject
AddHandler item.Click, AddressOf Item_Click
itemMenu.DropDownItems.Add(item)
Next
End Sub
Private Sub RemoveItems()
Dim itemMenu = DirectCast(ContextMenuStrip.Items.Find("name", False)(0), ToolStripMenuItem)
For Each item As ToolStripItem In itemMenu.DropDownItems
RemoveHandler item.Click, AddressOf Item_Click
Next
itemMenu.DropDownItems.Clear()
End Sub
Private Sub ContextMenuStrip_Opening(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles ContextMenuStrip.Opening
AddItems()
End Sub
Private Sub ContextMenuStrip_Closing(sender As Object, e As ToolStripDropDownClosingEventArgs) Handles ContextMenuStrip.Closing
RemoveItems()
End Sub
Private Sub Item_Click(sender As Object, e As EventArgs)
' Do the work
End Sub
The logic is to fill a sub-menu in a context menu each time with valid database objects.
The problem is that this code does not work. It adds the items to context menu perfectly but the AddHandler item.Click, AddressOf Item_Click does nothing.
The strange thing is that if I call the AddItems() in Form_Load then it works perfectly.
Any help would be appreciated.
2016.12.22 Solution after WozzeC answer
Private Sub AddItems()
RemoveItems()
Dim itemMenu = DirectCast(ContextMenuStrip.Items.Find("name", False)(0), ToolStripMenuItem)
For Each dbObject In dbObjects
Dim item As New ToolStripMenuItem(dbObject.Name)
item.Tag = dbObject
AddHandler item.Click, AddressOf Item_Click
itemMenu.DropDownItems.Add(item)
Next
End Sub
Private Sub RemoveItems()
Dim itemMenu = DirectCast(ContextMenuStrip.Items.Find("name", False)(0), ToolStripMenuItem)
For Each item As ToolStripItem In itemMenu.DropDownItems
RemoveHandler item.Click, AddressOf Item_Click
Next
itemMenu.DropDownItems.Clear()
End Sub
Private Sub ContextMenuStrip_Opening(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles ContextMenuStrip.Opening
AddItems()
End Sub
Private Sub Item_Click(sender As Object, e As EventArgs)
' Do the work
End Sub
I have been playing around with this for a little while now and I have managed to reproduce your issue. The reason for your headache is that RemoveItems fires before your Click event. So when you perform RemoveHandler, the click event disappear.
What I suggest that you do instead is to not add the click handler on the ContextMenuItems. Instead you add an EventHandler for DropDownItemClicked on the Parent node. The result will be the same as if Item.Click worked, but without the headache of handling Handlers dynamically.
I also tried switching from Closing to Closed event on the ContextMenu for RemoveItems. But to no avail I am afraid.
Another way to solve this issue is by moving RemoveItems into the first row of AddItems. Then you remove the closing event and its call to RemoveItems. This means that whenever you want to create a new ContextMenu the previous one is disposed properly. This will also solve your future bug where the ContextMenu items are added twice or more. Which currently happens for you if you right click multiple times really fast.

Passing the value from curElement to another private sub

Hi I have a For Each inside a Private sub and I want to pass the value of the cuElement to another Private sub.
Dim theAnchorsCollection As HtmlElementCollection = Nothing
theAnchorsCollection = WebBrowser1.Document.GetElementsByTagName("a")
For Each curElement As HtmlElement In theAnchorsCollection
curElement.Style = "style code here"
AddHandler curElement.Click, AddressOf OnaClick
Next
And I want to pass the value to this other sub:
Private Sub OnaClick(sender As Object, e As HtmlElementEventArgs)
Dim endereco As String
endereco = curElement.GetAttribute("href").ToString()
'Do other things
End Sub
I want to know if in this sub I can use something to get the value of the other's curElement.
Never mind, I found the answer it was (sender) the variable that I wanted.
As I found in the MSDN Forums
As you use Addhandler to subscribe for a given event, you pass required arguments of event to the procedure with AddressOf that delegates , whose argument signature "must" be same as event's.
A demo:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddHandler Button1.MouseDown, AddressOf btn_MouseDown
End Sub
Sub btn_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs)
MsgBox(e.Button.ToString)
End Sub
End Class
Using Addhandler, we're subscribing MouseDown event of button, and specifiying the procedure which gonna be called on the follow. The passed arguments are object(sender) and MouseEventArgs (e), and you can use this in your event sub like above. (eg: To get mousebutton that was down.)

Detecting the ID of checkbox that was checked

I am making a windows form application. I want to detect which checkbox is being selected by the user. One way to check this would be to loop through all the controls everytime a checkbox checkchanged event is fired. But I do not want to do that because multiple checkboxes could be checked. I want to get the ID of checkbox as it is selected or on a mousedown event. How can I do so?
You can add the event handlers for the checkboxes you want at runtime. Use the Where clause to filter by name if applicable. This code does it in form_load.
Inside the handler, you can cast sender to a local variable which represents the checkbox which was checked, if you want.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each checkBox In Me.Controls.OfType(Of CheckBox)().Where(Function(cb As CheckBox) cb.Name.Contains("CheckBox"))
AddHandler checkBox.CheckedChanged, AddressOf checkboxCheckedChanged
Next
End Sub
Private Sub checkboxCheckedChanged(sender As Object, e As EventArgs)
Dim myCheckbox As CheckBox = DirectCast(sender, CheckBox)
Dim c As Boolean = myCheckbox.Checked
Dim n As String = myCheckbox.Name
' etc.
End Sub
EDIT
As Neolisk pointed out, this doesn't account for nested controls, i.e. controls inside containers on the form. This extension method returns all those controls:
<Extension()> _
Public Function ChildControls(Of T As Control)(ByVal parent As Control) As List(Of T)
Dim result As New List(Of Control)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is T Then result.Add(ctrl)
result.AddRange(ctrl.ChildControls(Of T)())
Next
Return result.ToArray().Select(Of T)(Function(arg1) CType(arg1, T)).ToList()
End Function
And make this the loop in form_load instead:
For Each checkBox In Me.ChildControls(Of CheckBox).Where(Function(cb As CheckBox) cb.Name.Contains("CheckBox"))
AddHandler checkBox.CheckedChanged, AddressOf checkboxCheckedChanged
Next
Use sender argument in the event handler.
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) _
Handles CheckBox1.CheckedChanged
'DirectCast(sender, CheckBox).Name ?
End Sub

Me.FormClosing works on some forms but not others

I wish in my code to handle the case when the red X at the upper right of the form is clicked. To this end I consulted this and created an event handler thus:-
Private Sub DoFoo (sender As System.Object, e As System.EventArgs) _
Handles Me.FormClosing
' Do things
End Sub
but I have found (from setting breakpoints) that on certain forms this event handler is not invoked when the red X is clicked, whereas on others it is. The forms are all of type System.Windows.Forms.Form but naturally are different in most respects. Does anyone know what might be causing this and what to do about it?
Edit
In answer to Vitor's question, the form that isn't working is created thus:-
If my_form Is Nothing Then
my_form = New MyForm(parameters)
my_form.Title = "Contour Plot Options"
Else
my_form.BringToFront
End If
my_form.Show
Those that are behaving as expected are created like this:-
If my_working_form Is Nothing Then
my_working_form = New MyWorkingForm
End If
my_working_form.Show
I can't see any Visible property to set or clear anywhere.
Your parameters aren't quite right. A FormClosing event has a FormClosingEventArgs argument:
Private Sub DoFoo(ByVal sender As Object, ByVal e As FormClosingEventArgs) _
Handles Me.FormClosing
    If (e.CloseReason = CloseReason.UserClosing) Then
    End If
End Sub
You can inspect the e variable for the property `CloseReason', which would include a UserClosing enum, which means the user closed the form.
Each form should handle its own FormClosing event. Instead of subscribing to the event, I find it better to just override it like this:
Protected Overrides Sub OnFormClosing(ByVal e As FormClosingEventArgs)
If (e.CloseReason = CloseReason.UserClosing) Then
End If
MyBase.OnFormClosing(e)
End Sub
If you are instantiating your form you need to remember to AddHandler for the event you want to subscribe to.
my_form = New MyForm(parameters)
my_form.Title = "Contour Plot Options"
AddHandler my_form.Closing, AddressOf MyForm_Closing
'...
Sub MyForm_Closing(s As Object, ByVal e As FormClosingEventArgs)
'...
End Sub
Of course, to avoid memory leaks you should do it like this:
'global code
Private myFormClosingEventHandler As FormClosedEventHandler = Nothing
'...
Private Sub CreateForm
my_form = New MyForm(parameters)
my_form.Title = "Contour Plot Options"
myFormClosingEvent = New FormClosedEventHandler(AddressOf MyForm_Closing)
AddHandler my_form.Closing, myFormClosingEventHandler
'...
End Sub
Sub MyForm_Closing(s As Object, ByVal e As FormClosingEventArgs)
RemoveHandler my_form.Closing, myFormClosingEventHandler
'...
End Sub
Alternatively you can have it all pre-initalized for you by doing this in your class instead:
Private WithEvents my_form1 As Form = New Form()
Private WithEvents my_form2 As Form = New Form()
'... etc.
Now you can add in your code handlers using the Handle keyword as usual without using the AddHandler and RemoveHandler.
Protected Sub my_form1_Closing(s as object, e as FormClosingEventArgs) _
Handles my_form1.Closing
'...
End Sub