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

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

Related

Why doesn't Me.Controls.OfType work in VB.NET?

I'm using .NET Framework 4.7.2 for reference.
I'm using Me.Controls.OfType for automated handles in an event in my form.
Sub AddTextBoxes_TextChanged()
Dim textboxes = Me.Controls.OfType(Of TextBox)()
Console.WriteLine(textboxes.Count)
For Each txt In textboxes
AddHandler txt.TextChanged, AddressOf AllTextBoxes_TextChanged
Next
End Sub
Private Sub SampleForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddTextBoxes_TextChanged()
End Sub
Private Sub AllTextBoxes_TextChanged(sender As Object, e As EventArgs)
' ...
End Sub
However, the For loop doesn't work, so I checked if there are actual textbox controls within textboxes with Console.WriteLine(textboxes.Count). Well, the result is 0. I've checked multiple times in the Form Design for textboxes, and they exist. Why doesn't Controls.OfType(Of TextBox)() work?
Place your handler in the code for the form.
In design view select the one of the text boxes.
In the Properties window select the lightning bolt to display all the events available for a TextBox. Choose the TextChanged event and the drop down box arrow. Your AllTextBoxes_TextChanged method will be listed because the signature matches. Select your method and the Handles code will be added to the method.
Do the same for each text box you wish to use this method.
Of course you can always type the extended Handles clause.
However, I don't see what is wrong with your code.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim textboxes = Controls.OfType(Of TextBox)() '.ToList
For Each txt In textboxes
AddHandler txt.TextChanged, AddressOf AllTextBoxes_TextChanged
Next
End Sub
Private Sub AllTextBoxes_TextChanged(sender As Object, e As EventArgs)
Dim tb = DirectCast(sender, TextBox)
MessageBox.Show($"The text changed event fired by {tb.Name}")
End Sub
Works for me.

Working sample of Control.VisibleChanged Event in vb.net

I'm struggling to make the MSDN code sample for the Control.VisibleChanged event work: I don't see the MsgBox.
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs)
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub AddVisibleChangedEventHandler()
AddHandler myLabel.VisibleChanged, AddressOf Label_VisibleChanged
End Sub 'AddVisibleChangedEventHandler
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
You need to "wire up" the events to the event handlers.
To start with, to get the code in HideLabel_Click to be called you need it to respond to a click on the button named "HideLabel".
There are two ways to do that: you can use AddHandler or the Handles clause.
To demonstrate the latter:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub myLabel_VisibleChanged(sender As Object, e As EventArgs) Handles myLabel.VisibleChanged
MessageBox.Show("Visible change event raised!!!")
End Sub
End Class
However, you will notice that the message is shown even before the form appears. That is because of what goes on behind the scenes to create the form.
To avoid that happening, you can add the handler after the form has been shown:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub myLabel_VisibleChanged(sender As Object, e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
AddHandler myLabel.VisibleChanged, AddressOf myLabel_VisibleChanged
End Sub
End Class
Another way, in VB2015 and later, is to use a "lambda expression" instead of a separate method, although then you cannot disassociate the handler from the event with RemoveHandler:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
AddHandler myLabel.VisibleChanged, Sub() MessageBox.Show("Visible change event raised!!!")
End Sub
End Class
Craig was kind enough to [and I quote verbatim] call attention to the importance of Option Strict when you add handlers manually using AddHandler. Without it, the "relaxed delegate convention" may allow adding handlers which don't exactly match the event signature that you won't be able to remove later.
Having said that, Option Strict On isn't a complete safeguard: notice how my last example compiles and works even with the wrong method signature for the handler.
[I suspect that the MSDN code sample was first created in C# as part of a larger example, so some parts have been lost in the translation and excerption.]
I get this is old but came across this post when looking for more information on VisibleChanged and couldn't help but notice that the accept answer may be misleading. If you are using a designer to create your Form and place objects on it, then the accepted answer will be fine. In fact you can get rid of the addHandler because the designer handles that for you. All you would need to do is use a handles clause with your label.
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs) Handles myLabel.VisibleChanged
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Where the issue lies with the accepted answer is if you arn't using a designer. Adding handle clauses to "wire up" simply won't work (we can make it work and if anyone is interested in that I'll be happy to post a code snippet of that, but it's not how the accepted answer lays it out). In your case all you need to do is call AddVisibleChangedEventHandler() to set up the handler. that's it. you could have done this by calling it in MyBase.Load
Private Sub Load_Form(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
AddVisibleChangedEventHandler()
End Sub
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs)
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Private Sub AddVisibleChangedEventHandler()
AddHandler myLabel.VisibleChanged, AddressOf Label_VisibleChanged
End Sub
Once again I know this is dated but couldn't help but notice that (more or less assuming) that you are trying to get a msgBox to appear when you click a label. That is you click a label and then toggled the visibility of another label. The other label is the one where the event handler is on for visibility change. So that inevitably gets called when clicking the original label. IF you only want this msgBox to appear when clicking that label and not when the form loads as well, you should change the addHandler statement so that you are adding a handler on the click event.
Private Sub Load_Form(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
AddVisibleChangedEventHandler()
End Sub
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Private Sub AddVisibleChangedEventHandler()
AddHandler otherLabel.Click, AddressOf Label_VisibleChanged
End Sub 'AddVisibleChangedEventHandler
Also Option Strict On has nothing to do with addhandler (From my understanding, could be wrong. please enlighten me if that is the case). Option Strict On is only checking to see that you arn't implicitly typecasting. So for example:
Dim a As Double
Dim b As Integer
a = 10
b = a
results in an error when Option Strict is On but is totally legal if it is off. So in the case of you leaving off the handles clause, you'll never be implicitly typecasting and therefore is not needed.
Hope this helps anyone who sees this question

Run some code when any control on a list loses focus

Currently, I have this, on my Form.vb:
Private Sub txtBox1_Leave(sender As Control, e As EventArgs) Handles txtBox1.Leave
'Some code
End Sub
...
Private Sub txtBox10_Leave(sender As Control, e As EventArgs) Handles txtBox10.Leave
'Some code
End Sub
The thing that bothers me is: All those events are doing the same thing. Is it possible to programatically have a list of the relevant controls and iterate through them, adding such events? This would allow me to reduce the amount of code in my application / coding effort. Something like:
For Each c As Control in listOfControls
'Add event for c here which calls method
Next
I really think there is a simple way of doing that but everything I've tried so far (such as AddHandler) did not work. Any ideas?
Thank you
Yes it is possible and quite simple, create a method that will add the requested event to the Control, pass an array of controls to that method before the form loads:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' add events to all requested controls
AddEvent(New Control() {TextBox1, TextBox2, TextBox3, Button1})
End Sub
Public Sub AddEvent(ByVal myControls() As Control)
For Each c As Control In myControls
AddHandler c.Leave, AddressOf Control_Leave
Next
End Sub
Private Sub Control_Leave(sender As Object, e As EventArgs)
MsgBox("Control is not in focus")
End Sub
End Class

How to select TextBox on Label Click

In Microsoft Access, when you click on a label, the textbox associated with that label gets the focus. As far as I can tell, VB.NET does not have this same functionality. I know I can always add something in to the click event of the label, like so...
TextBox1.Focus()
But I have dozens of fields on the form, and it would make it so much easier if I did not need to add this to the click event of each label.
I guess it would be possible to make an event for all labels that forces a tab to the next control, and assuming that I have the Tab indices set up correctly, then this would work. The problem would occur when adding new fields to the form, in which case all tab indices would need re-verified.
Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click, Label2.Click
'code to tab to next field...
End Sub
Is there any easier way?
First, set the controls' TabIndex orders on your form then use this code:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For Each c As Control In Me.Controls
If TypeOf c Is Label Then AddHandler c.Click, AddressOf Label_Click
Next
End Sub
Private Sub Label_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Me.SelectNextControl(sender, True, True, True, True)
End Sub
End Class
Now whenever you click on a label, the following control in the order will be focused.
How about creating a dictionary where the label is the key and the control to focus is the value, then add a Click event handler to all of the label in the Dictionary. Everytime you add a label/control 'all' you need to do is add another KeyValuePair to the Dictionary
Simple Example:
Public Class Form1
Protected Friend DicLabelToControl As Dictionary(Of Label, Control)
Protected Friend Sub InitLabelDic()
DicLabelToControl = New Dictionary(Of Label, Control) From {{Label1, TextBox1}, {Label2, TextBox2}}
End Sub
Protected Friend Sub AddClickEventsToLabels()
For Each lb As Label In DicLabelToControl.Keys
AddHandler lb.Click, AddressOf HandleLabelClick
Next
End Sub
Private Sub HandleLabelClick(sender As Object, e As EventArgs)
DicLabelToControl(CType(sender, Label)).Focus()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitLabelDic()
AddClickEventsToLabels()
End Sub
End Class

Drop onto FlowLayoutPanel

Hi guys Hope all is well
I am wondering(struggling) the following:
I have 5 flowLayoutPanels and 5 PictureBoxes i want to be able to move anyone of the picture boxes over anyone the FLP at run time and have the layout panel add it to FLP.controls.Add()....
I've been at it for Hours and now ill swallow my pride -
I have done the following To get it working, but here i have to manually specify which PixBox intersects with which FLP and i dont want 25 if statements
Private Sub cpbPic1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles cpbPic1.MouseUp
If (flpDock1.HasChildren = False) Then 'Test to see if panel is filled
If CBool(CustomPictureBox.IntersectingObjects(cpbPic1, flpDock1)) Then
flpDock1.Controls.Add(cpbPic1) 'Add Pic to Panel
End If
End Sub
cpb: CustomPictureBox
you could always do this:
Private Sub cpbPic1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles cpbPic1.MouseUp, cpbPic2.MouseUp, cpbPic3.MouseUp,cpbPic4.MouseUp,cpbPic5.MouseUp
If Not flpDock1.HasChildren Then 'Test to see if panel is filled
If CBool(CustomPictureBox.IntersectingObjects(TryCast(sender,CustomPictureBox), flpDock1)) Then
flpDock1.Controls.Add(TryCast(sender,CustomPictureBox)) 'Add Pic to Panel
End If
End Sub
This will reduce the amount of code you'll have to write significantly, you can further reduce this amount if you think about how to utilize the fact that the event handler passes the Object which raises the flag, like I did here.
Also you can use arbitrary big amount (i think) of objects in a handler as long as they raise the same event
well this can be a work around for what you want to do.
you also have to enable allowdrop to the flowpanels
Private Function FindControl(ByVal ControlName As String, ByVal CurrentControl As Control) As Control
' get the control you need
Dim ctr As Control
For Each ctr In CurrentControl.Controls
If ctr.Name = ControlName Then
Return ctr
Else
ctr = FindControl(ControlName, ctr)
If Not ctr Is Nothing Then
Return ctr
End If
End If
Next ctr
End Function
Private Sub me_DragEnter(sender As Object, e As DragEventArgs) Handles FLP1.DragEnter,FLP2.DragEnter,FLP3.DragEnter
' call the copy effect
If (e.Data.GetDataPresent(DataFormats.Text)) Then
e.Effect = DragDropEffects.Copy
End If
End Sub
Private Sub me_DragDrop(sender As Object, e As DragEventArgs) Handles FLP1.DragDrop,FLP2.DragDrop,FLP3.DragDrop
' get the FLp you're gonna drop the control onto
Dim c As control =FindControl(e.Data.GetData(DataFormats.Text), me)
sender.Controls.Add(c)
end sub
Private Sub Pictureboxs_MouseDown(sender As Object, e As MouseEventArgs) Handles Label1.MouseDown, PB.MouseDown
sender.DoDragDrop(sender.Name, DragDropEffects.Copy)
End Sub
hope that this helps you :) (sorry for my bad english)