VB.net - How to send multiple senders to event handler - vb.net

I have MonthlyCalendar created and shown once a text box is clicked "e.g. Date of Birth". I do have multiple dates text boxes in my Form and I would like to send the actual text box as Object "clicked" along with the MonthlyCalendar as Sender as well , 3 arguments.
Private Sub TextBox1_Click(sender As Object, e As EventArgs) Handles txtDOB.Click
Dim mc = New MonthCalendar()
AddHandler mc.DateSelected, AddressOf DateSelected
Me.Controls.Add(mc)
mc.BringToFront()
mc.Show()
End Sub
Private Sub DateSelected(sender As Object, e As DateRangeEventArgs) Handles MonthCalendar1.DateSelected
txtDOB.Text = DirectCast(sender, MonthCalendar).SelectionRange.Start.Date.ToString("yyyyMMdd")
End Sub
I would like the handler to be like this:
1st argument the actual textbox clicked 2nd argument the Monthly Calendar 3rd argument the DateRangeEventArgs
AddHandler mc.DateSelected, AddressOf DateSelected(sender 'Textbox as Object, sender 'MontlyCalendar As Object, e As DateRangeEventArgs)
Appreciate guidance and a better way of doing this

Here's the way I'd do this. I'd avoid all of that casting you're doing.
Private Sub AnyTextBox_Click(sender As Object, e As EventArgs) Handles txtDOB.Click, txt2.Click, txt3.Click
Dim tb = CType(sender, TextBox)
Dim mc = New MonthCalendar()
AddHandler mc.DateSelected, Sub (s2, e2) mc.SelectionRange.Start.Date.ToString("yyyyMMdd")
Me.Controls.Add(mc)
mc.BringToFront()
mc.Show()
End Sub
This also avoids the separate DateSelected handler. Much cleaner and even more strongly typed.
The next step in making the code cleaner is to handle the closing down of your MonthCalendar control. Your current code and my code above doesn't do it.
Here's how:
Private Sub AnyTextBox_Click(sender As Object, e As EventArgs) Handles txtDOB.Click, txt2.Click, txt3.Click
Dim tb = CType(sender, TextBox)
Dim mc = New MonthCalendar()
Dim handler As EventHandler(Of DateRangeEventArgs) = _
Sub(s2, e2)
mc.SelectionRange.Start.Date.ToString("yyyyMMdd")
Me.Controls.Remove(mc)
RemoveHandler mc.DateSelected, handler
End Sub
AddHandler mc.DateSelected, handler
Me.Controls.Add(mc)
mc.BringToFront()
mc.Show()
End Sub

You don't send anything to an event handler. The object itself raises the event in response to something done to it and it sends the arguments to the event handler. You can't change the signature and you can't choose what arguments it receives.
Probably your best bet would be to, when you create the MonthCalendar, assign the corresponding TextBox to its Tag property.
Dim mc = New MonthCalendar With {.Tag = sender}
Tag is a general-purpose data property of type Object, so there's no need to cast sender at this point. You know that the actual object is a TextBox, because that's all that will raise that event.
In the event handler, you can then get the MonthCalendar from the sender and the TextBox from its Tag.
Dim mc = DirectCast(sender, MonthCalendar)
Dim tb = DirectCast(mc.Tag, TextBox)

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.

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

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

sub event to interact with more than one control

I want to call this snippet passing a "controlname" like a argument, then the sub interacts with the desired control
How I can do that?
This is the snippet:
#Region " Move a control in real-time "
' Change Textbox1 to the desired control name
Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles textbox1.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
textbox1.Capture = False
Dim ControlMoveMSG As Message = Message.Create(textbox1.Handle, &HA1, New IntPtr(2), IntPtr.Zero)
Me.DefWndProc(ControlMoveMSG)
End If
End Sub
#End Region
UPDATE:
The solution:
Private Sub MoveControl(sender As Object, e As EventArgs) Handles _
TextBox1.MouseDown, _
TextBox2.MouseDown, _
PictureBox1.MouseDown
Dim control As Control = CType(sender, Control)
control.Capture = False
Dim ControlMoveMSG As Message = Message.Create(control.Handle, &HA1, New IntPtr(2), IntPtr.Zero)
Me.DefWndProc(ControlMoveMSG)
End Sub
In this case, you can just use sender. The sender parameter is a reference to whichever control is raising the event. So, if you add this same method as an event handler for multiple controls, sender will be which ever control raised the event that it's currently handling, for instance:
Private Sub MouseDown(sender As Object, e As EventArgs) _
Handles TextBox1.MouseDown, TextBox2.MouseDown
' Note in the line above that this method handles the event
' for TextBox1 and TextBox2
Dim textBox As TextBox = CType(sender, TextBox)
' textBox will now be either TextBox1 or TextBox2, accordingly
textBox.Capture = False
' ....
End Sub
The CType statement casts the base Object parameter to the specific TextBox class. In this example, the method only handles events for TextBox objects, so that will work. However, if you have it handle events from other types of controls, you'd need to cast to the more general Control type (i.e. CType(sender, Control)).

Creating a Combobox from a Class - In VB.net

im new to vb.net, and my query is:
file: read.vb
Friend Function Nav()
Dim NavBox As New ComboBox()
NavBox.Size = New System.Drawing.Size(44, 21)
NavBox.Location = New System.Drawing.Point(135, 305)
NavBox.DropDownStyle = ComboBoxStyle.DropDownList
NavBox.Items.Add("1")
NavBox.Items.Add("2")
NavBox.Items.Add("3")
NavBox.Items.Add("4")
NavBox.Items.Add("5")
NavBox.Items.Add("6")
NavBox.Items.Add("7")
NavBox.Items.Add("8")
NavBox.Items.Add("9")
NavBox.Items.Add("10")
NavBox.Items.Add("11")
AddHandler (NavBox.SelectionChangeCommitted), AddressOf MSGB
NavBox.Show()
Return NavBox
End Function
Public Sub MSGB(ByVal sender As Object, ByVal e As System.EventArgs)
Dim cb As ComboBox = DirectCast(sender, ComboBox)
MsgBox(cb.SelectedItem)
End Sub
And im calling this function or displaying this combobox in mainFrom.vb
file: mainFrom.vb
Dim l As New read
Me.Controls.Add(CType(l.Nav(), Control))
Now what i need is, a access to its control so i can parss value from mainFrom.vb to read.vb to manipulation the SelectionChangeCommitted activity.
I hope my question is clear....
Since you control is a simple ComboBox, I don't see the point of having common code to create this control. You can have a common function to populate it's contents though.
If you want to add functionality to the ComboBox, just create your own combo box class, let's say ExtendedComboBox, which inherits from ComboBox, and use it in your forms.
If you don't want to change your existing code, you can assign your ComboBox returned from Nav to a variable, and then hook into events from this ComboBox:
Public Class MainForm
'Declare NavBox as a form member
Private NavBox As ComboBox
Public Sub MainForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Create NavBox
Dim read As New read
NavBox = read.Nav()
'Hook NavBox event so MainForm knows when selection is changed
AddHandler NavBox.SelectionChangeCommitted, AddressOf NavBox_SelectionChangedCommited
'Add NavBox to MainForm
Me.Controls.Add(NavBox)
End Sub
Public Sub NavBox_SelectionChangedCommited(ByVal sender As Object, ByVal e As System.EventArgs)
Dim cb As ComboBox = DirectCast(sender, ComboBox)
'Do something when selection changes.
End Sub
End Class
You should also remove the useless AddHandler line inside the Nav function.