How to add event handler to a dynamically created control in VB.NET? - vb.net

I have searched and seen countless samples here in this forum and in other sites but I'm still stuck with this problem;
I want to add a Click Handler for dynamically created PictureBox-es and pas an argument on it so I know which one of picture boxes was clicked).
Here is my current code:
Public Class frmMbarimAbonimi
Private Sub frmMbarimAbonimi_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'FitnessdbDataSet.clients' table. You can move, or remove it, as needed.
'Me.ClientsTableAdapter.Fill(Me.FitnessdbDataSet.clients)
'===============
Dim dt As DataTable = PaPaguar()
Dim i As Integer = 0
Dim gr(dt.Rows.Count) As GroupBox
Dim pp(dt.Rows.Count) As PictureBox
Dim lb(dt.Rows.Count) As Label
For Each row As DataRow In dt.Rows
gr(i) = New GroupBox
gr(i).Width = 200
gr(i).Height = 180
pp(i) = New PictureBox
pp(i).SizeMode = PictureBoxSizeMode.StretchImage
lb(i) = New Label
'-------------------------
Try
Using str As Stream = File.OpenRead("C:\Fotot\" + dt.Rows(i).Item("Foto"))
pp(i).Image = Image.FromStream(str)
End Using
lb(i).Text = dt.Rows(i).Item("Emer")
Catch ex As Exception
MsgBox("Fotoja nuk mund te ngarkohet, ju lutem realizoheni nje foto tjeter!!!")
End Try
'-------------------------
pp(i).Visible = True
pp(i).Width = 200
pp(i).Height = 150
AddHandler pp(i).Click, AddressOf testini
gr(i).Controls.Add(pp(i))
lb(i).Visible = True
lb(i).Width = 200
lb(i).Height = 30
lb(i).Left = pp(i).Left
lb(i).Top = pp(i).Top + 150
lb(i).BackColor = Color.WhiteSmoke
lb(i).BringToFront()
gr(i).Controls.Add(lb(i))
flpanel.Controls.Add(gr(i))
i = i + 1
Next row
End Sub
End Class
So I was trying to use AddHandler pp(i).Click, AddressOf testini but obviously this does not allow me to call "testini" with a parameter to identify which picture box was clicked.
Can someone point me in the right direction or give some advice? Greatly appreciated.

You need to add something to your created PictureBox to identify them in the event handler because you can't change the signature of the click event handler adding a 'parameter'
For example, you could set the Name property
pp(i) = New GroupBox
pp(i).Name = "PictureBox" + i.ToString
then in the event handler you could recognize your picture box casting the sender object to a picturebox and grabbing the Name property.
Remember, sender is always the control that triggers the event. In your case is always one of your dinamically created PictureBoxes
Private Sub testini(sender As Object, e As System.EventArgs)
Dim pb As PictureBox = DirectCast(sender, PictureBox)
Dim pbIdentity As String = pb.Name
.....
End Sub

Related

Identify buttons on click?

So here's what I have:
Code that makes buttons when you click the button "New Button" (Button1).
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ActionsMade = ActionsMade + 1
Dim actionButton = New Windows.Forms.Button
ActionContainer.Controls.Add(actionButton)
actionButton.Text = "New Action " + ActionsMade.ToString
actionButton.Width = 107
actionButton.Height = 56
AddHandler actionButton.Click, AddressOf OnActionButtonClick
End Sub
When you click the buttons that were created, it shows an input box, where you type what you want to rename the button to.
Sub OnActionButtonClick()
If IsRenaming Then
Dim renameTo As String
renameTo = InputBox("Rename ActionButton To:")
Else
MessageBox.Show("Action started! Not renaming")
End If
End Sub
The only problem is, I have no way to identify between these buttons.
tl;dr:
I created buttons programmatically
I want to rename them
I don't know how to identify between these buttons and rename them individually
This is more concise and includes a referencable button name (eg Button_03):
Private Sub MakeButton_Click(sender As Object, e As EventArgs) Handles MakeButton.Click
Static ActionsMade As Integer = 0
ActionsMade += 1
ActionContainer.Controls.Add(New Button With {
.Name = $"Button_{ActionsMade:00}",
.Text = $"New Action {ActionsMade:00}",
.Size = New Size(107, 56),
.Location = New Point(100, 250 + 25 * ActionsMade)
})
AddHandler ActionContainer.Controls($"Button_{ActionsMade:00}").Click, AddressOf OnActionButtonClick
End Sub
Note that in the above code I haven't actually defined a new button per se, just New-ed it within the Controls.Add() statement, and then used the generated button name to add the event handler.
Another way would be declare the button as an independent object and then add it:
Dim actionButton As New Button With {
.Name = $"Button_{ActionsMade:00}",
.Text = $"New Action {ActionsMade:00}",
.Size = New Size(107, 56),
.Location = New Point(100, 250 + 25 * ActionsMade)
}
ActionContainer.Controls.Add(actionButton)
AddHandler actionButton.Click, AddressOf OnActionButtonClick
Other notes:
Use of a Static ActionsMade integer variable which retains its value between calls. This could also be a module-wide variable and removed from the MakeButton_Click Sub.
Use of string interpolation in assigning the button's Name and Text properties according to a formated version of the ActionsMade value.
Use of button properties Size and Location.
Automatic increment of button vertical position based on the value of ActionsMade.
Regarding your Action Button event handler, it should look like this:
Private Sub OnActionButtonClick(sender As Object, e As EventArgs)
If ActionStarted Then
MessageBox.Show("Action started! Not renaming.")
Else
CType(sender, Button).Text = InputBox("Rename ActionButton To:")
End If
End Sub
Note that I've checked for a started Action rather than a renaming in progress but that's just an illustration of an alternative approach. Note also that you don't need to know the name of the button to change its properties.

How to add infinite components when a button is clicked

I have a social media WinForm. I have a function that basically makes a new picture box when a button is clicked
Public Sub NewPost()
picture as new picturebox
picture.Width = 208
picture.Height = 264
picture.Image = Form2.PictureBox1.Image
picture.Location = New Point(258, 60)
End Sub
The thing is it only generates 1 new picture box because I have to make a new variable each time I want to add a picturebox, and eachtime I have to have a new name. I know my question Is a bit confusing but help would be nice thanks
If you want to trap events for your dynamic PictureBoxes, then you'll have to abandon the WithEvents model and move to using AddHandler.
Here's a quick example where the name of the PictureBox is displayed when it is clicked. Note that I am not setting a Location since they are being added to a FlowLayoutPanel which takes care of the placement for you:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
NewPost()
End Sub
Public Sub NewPost()
Dim picture As New PictureBox
picture.Width = 208
picture.Height = 264
picture.BorderStyle = BorderStyle.FixedSingle
' ...etc...
Dim index As Integer = FlowLayoutPanel1.Controls.Count + 1
picture.Name = "pb" & index
AddHandler picture.Click, AddressOf picture_Click
FlowLayoutPanel1.Controls.Add(picture)
End Sub
Private Sub picture_Click(sender As Object, e As EventArgs)
Dim pb As PictureBox = DirectCast(sender, PictureBox)
Debug.Print(pb.Name)
End Sub
End Class
because I have to make a new variable each time
Not necessarily. You just want to keep a reference to the object. That reference doesn't need to be its own variable, it can just as easily be an element in a list. For example, suppose on your form you have a list of PictureBox objects as a class-level member:
Dim pictureBoxes As New List(Of PictureBox)()
Then in your method you can just add to that list:
Public Sub NewPost()
Dim pictureBox As New PictureBox
pictureBox.Width = 208
pictureBox.Height = 264
pictureBox.Image = Form2.PictureBox1.Image
pictureBox.Location = New Point(258, 60)
Me.pictureBoxes.Add(pictureBox)
End Sub
In this case the pictureBox variable is local to the NewPost method and gets re-created each time. But pictureBoxes is a class-level member and keeps track of the growing list of PictureBox objects that you're creating.
You can use a for while loop to create n number of objects
You can use the existing ControlCollection
Public Function NewPost() As String
Dim picture As New PictureBox
'your code
picture.Name = "Pb" & Form2.Controls.OfType(Of PictureBox).Count
Form2.Controls.Add(picture)
Return picture.Name
End Function
then you can retrive it
DirectCast(Form2.Controls(NewPost), PictureBox).Image = Form2.PictureBox1.Image
'OR
DirectCast(Form2.Controls("Pb12"), PictureBox).Image = Form2.PictureBox1.Image

Button Array - how to pass a parameter to shared handler

I have a bit of code where i have a dynamically created array or buttons with staff pictures on them, as well as the staff's name. I've added one handler to handle any button click from any of the buttons. where i am stuck is, if you look at the code below, it all works fine, and if you click any of the buttons you get the "aha" test message. but i want the name of the staff clicked on (so btnArray(i).Text) to be passed to the handler for further processing. I tried adding a ByVal parameter to the handler but that caused an error. what's the correct way to do this? As i said, the code below works for me, i just am at a loss as to how to add the extra functionality.
Dim btnArray(staffcount) As System.Windows.Forms.Button
For i As Integer = 1 To staffcount - 1
btnArray(i) = New System.Windows.Forms.Button
btnArray(i).Visible = True
btnArray(i).Width = 80
btnArray(i).Height = 101
btnArray(i).BackgroundImage = Image.FromFile(picloc(i))
btnArray(i).BackgroundImageLayout = ImageLayout.Stretch
btnArray(i).Text = staffname(i)
Dim who As String
who = btnArray(i).Text
AddHandler btnArray(i).Click, AddressOf Me.theButton_Click
btnArray(i).ForeColor = Color.White
btnArray(i).TextAlign = ContentAlignment.BottomCenter
Dim fnt As Font
fnt = btnArray(i).Font
btnArray(i).Font = New Font(fnt.Name, 10, FontStyle.Bold)
FlowLayoutPanel1.Controls.Add(btnArray(i))
Next i
End Sub
Private Sub theButton_Click()
MsgBox("aha")
End Sub
First, correct the signature of your shared handler.
Private Sub theButton_Click(sender As Object, e As EventArgs)
End Sub
Once that is done getting the text of the button clicked is a simple matter.
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim textOfButtonClicked As String = DirectCast(sender, Button).Text
MessageBox.Show(textOfButtonClicked)
End Sub
The sender is the button that was clicked. Since signatures use objects for the sender the DirectCast 'changes' it to button and you then can access the .Text property of the button.
If there are more manipulations you want to perform on the clicked button you could do it this way
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim whBtn As Button = DirectCast(sender, Button) ' get reference to button clicked
Dim textOfButtonClicked As String = whBtn.Text
MessageBox.Show(textOfButtonClicked)
'e.g. change the color
whBtn.BackColor = Color.LightYellow
End Sub

How do I add then select the dynamic control I just added

I've got an application in VB.net which adds a picturebox to the selected mouse position within another picturebox control. I need to create a click event to select that new picturebox so I can drag it and drop it to a new location in the event that the first one was wrong or use a keypress event, those events I will code later, but I can not figure out how to select ANY of the dynamic controls.
In vb6 there was a way to select an index of the control, but there is no such animal in VB.net.
I've tried control groups, but for some reason I'm not getting results from them.
Here is the code I have so far
Private Sub PictureBox1_Click(sender As System.Object,
e As System.EventArgs) Handles PictureBox1.Click
Dim pb As New PictureBox
pb.BackColor = Color.Blue
Me.PictureBox1.Controls.Add(pb)
pb.Size = New Size(64, 110)
pb.Location = New Point(Cursor.Position.X - 64, Cursor.Position.Y - 110)
pb.Visible = True
End Sub
What in the name of all good things am I doing wrong here?
You need to write a generic event handler before time, using the sender parameter to refer to the object that raised the event.
Private Sub PictureBoxes_Click(sender As Object, e As EventArgs)
Dim pb = DirectCast(sender, PictureBox)
'Use pb here.
End Sub
When you create your control at run time, use an AddHandler statement to attach the method to the event.
Dim pb As New PictureBox
AddHandler pb.Click, AddressOf PictureBoxes_Click
That said, if you want to implement drag-n-drop then it's not the Click event you should be handling.
This little bit of code took some time, but I was able to do what I set out to so far...
This is before the Sub Main event
Public Class dynamicPB 'create a picturebox element which
'can be called anytime
Inherits PictureBox 'sets the type of control to a
'picturebox
Public Sub New() 'sets the function of the new box to
'default values
MyBase.BackColor = Color.AliceBlue
MyBase.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
MyBase.Height = 50
MyBase.Width = 26
End Sub
End Class
in the actual main class
Private Sub <control_event> (blah...) Blah...
Dim intPosAdj_X As Integer = 13 'get an offset for the cursor
Dim intPosAdj_Y As Integer = 25
Dim newPictureBox As New dynamicPB 'sets the click of the mouse into a
'mode of drawing a new PB
With newPictureBox 'clean use of the code
AddHandler newPictureBox.Click, _
AddressOf PictureBox_Click 'establishes events for the mouse
'activity on the objects
AddHandler newPictureBox.MouseEnter, _
AddressOf PictureBox_MouseEnter
AddHandler newPictureBox.MouseLeave, _
AddressOf PictureBox_MouseLeave
pbName += 1 'gives a unique name to the
'picturebox in an "array" style
.Location = New System.Drawing.Point _
(xPos - intPosAdj_X, yPos - intPosAdj_Y) 'establish where the box goes
'and center the object on the
'mouse pointer
.Visible = True 'ensures that the box is visible
.Name = pbName 'names the new control
End With
Controls.Add(newPictureBox) 'add control to form
End Sub
Private Sub PictureBox_Click(sender As System.Object, e As System.EventArgs)
Dim dblMouseClick As Double = CType(DirectCast _
(e, System.Windows.Forms.MouseEventArgs).Button, MouseButtons) _
'make it simple to manipulate
'the event by putting the long
'code into a variable
If dblMouseClick = MouseButtons.Left Then
MsgBox("Left CLick")
ElseIf dblMouseClick = MouseButtons.Right Then
MsgBox("right click")
Else
MsgBox("Center")
End If
This actually resolves the issue of adding and being able to select the object
Thanks for all of the suggestions and help

Programmatically setting properties of controls on tab pages of a tabcontrol

I am working with a tabcontrol on which I create page one with the designer. I am creating new tab pages with controls on the pages programmatically. On each page is a several panels, each with two radiobuttons (one yes,another no). There is a panel nested inside the first panel with its visible property set to false. If the user selects yes, I want the nested panel's visible property set to true which will reveal several more radiobuttons from which they must make more choices.
My problem is in changing the nested panel's property on any page other than page one.. I can detect the radiobuttons, but I can't seem to find a way to make the nested panel visible.
Public Class ControlProgram
Dim pageindx as integer
Private Sub btnAddPrgm1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAddPrgm1.Click
Dim newTab As New TabPage()
pageindx = (TabControl1.TabPages.Count + 1)
newTab.Text = "Step " & pageindx
'define fill panel controls
Dim newpnlFill As New Panel
Dim newlblFill As New Label
Dim newFillY As New RadioButton
AddHandler newFillY.CheckedChanged, AddressOf CheckforCheckedChanged
Dim newFillN As New RadioButton
AddHandler newFillN.CheckedChanged, AddressOf CheckforCheckedChanged
'add fill panel controls
With newTab.Controls
.Add(newpnlFill)
With newpnlFill
.Location = New System.Drawing.Point(6, 6)
.Size = New System.Drawing.Size(171, 137)
.BorderStyle = BorderStyle.FixedSingle
.Controls.Add(newlblFill)
With newlblFill
.Name = "Fill"
.Text = "Fill ?"
.Font = New Font(newlblFill.Font, FontStyle.Bold)
.Location = New Drawing.Point(5, 3)
End With
.Controls.Add(newFillY)
With newFillY
.Name = "FillY"
.Text = "Yes"
.Location = New Drawing.Point(23, 28)
.Size = New System.Drawing.Size(43, 17)
End With
.Controls.Add(newFillN)
With newFillN
.Name = "FillN"
.Text = "No"
.Location = New Drawing.Point(88, 28)
.Size = New System.Drawing.Size(39, 17)
End With
.Controls.Add(newpnlFill2)
With newpnlFill2
.Location = New System.Drawing.Point(2, 60)
.Size = New System.Drawing.Size(164, 68)
.BorderStyle = BorderStyle.FixedSingle
.Visible = False
End With
End With
End With
Private Sub CheckforCheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf sender Is RadioButton Then
bEvent = CType(sender, RadioButton).Name
End If
End Sub
End Class
I have since figured out a solution to my delima, using your suggestions as a starting point.
I added a few varribles:
Dim rb as Control
Dim bEvent as String
Dim booFillY as Boolean
Dim booFillN as Boolean
I also added the TabControl
TabControl1.TabPages.Add(newTab)
I also made these changes :
Private Sub CheckforCheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf sender Is RadioButton Then
rb = sender
bEvent = CType(sender, RadioButton).Name
If bEvent = "FillY" Then
Dim newpnlFill2 As Panel = rb.Parent.Controls(3)
newpnlFill2.Visible = True
End If
If bEvent = "FillN" Then
Dim newpnlFill2 As Panel = rb.Parent.Controls(3)
newpnlFill2.Visible = False
End If
End If
End Sub
Now I can make the nested panel(newpnlFill2) visible or not visible by cicking the Yes or No radiobuttons on any of the tab pages created.
thanks for your help. I doubt I would have ever gotten there on my own.
Might not be quite what you were looking for, but should be helpful get you where you need to go.
When I create an application, I like to build a list of all the controls for a given page in the load event so I can access them at any point. This is helpful because WinForms can be very picky about showing you child controls within a tabpage or groupbox, etc.
'Declare this variable within the class for your form (whatever)
Public arrControlsRecursive As New List(Of Control)
'method to recursively check all controls and build to array
Private Sub BuildControlsArrayRecursive(ByVal InControl As Control)
For Each con As Control In InControl.Controls
If con.Controls.Count > 0 Then
BuildControlsArrayRecursive(con)
End If
If TypeOf con Is Control Then
arrControlsRecursive.Add(con)
End If
Next
End Sub
'Call from MyBase.Load Event
BuildControlsArrayRecursive(Form1)
You can also just assemble a list of all tabs, for example, by changing the If statement to Is TypeOf con Is TabPage
Now you can loop through this collection or query it with LINQ. Find a single control by calling the first or single method. Cast to the type you want and do anything to any control anywhere within your form.
I don't really understand what you want to access, you first talk about
changing the nested panel's property on any page other than page one
So I assume you want to access to the other tabs, then, you talk about:
I can't seem to find a way to make the panel visible
Anyway, here's the two solutions:
Access other panels:
Private Sub CheckforCheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf sender Is RadioButton Then
bEvent = CType(sender, RadioButton).Name '**Where is "bEvent" declared??**
Dim newpnlFill2 as Panel = bEvent.Parent.Controls(3), Panel)
newpnlFill2.Visible = bEvent.Checked
End If
End Sub
You access to Parent that will be newpnlFill, then access to Controls(3) that it should be newpnlFill2.
Access other tabs:
Private Sub CheckforCheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If TypeOf sender Is RadioButton Then
bEvent = CType(sender, RadioButton).Name '**Where is "bEvent" declared??**
Dim TabControl as TabControl = bEvent.Parent.Parent.Parent, TabControl)
'Then, you can access all of the other tabs with:
'TabControl.TabPages(n)
End If
End Sub
This assume that somewhere you add your newTab to a TabControl.
I see that you never add newTab to any TabControl, so you'll never see it..
The first Parent will be newpnlFill, the second one will reference to newTab and the last one is the TabControl that hold the Tab.
Anyway, it's something really gross, cause it assumes that your Tab is always created in this manner. For example, if you will add another panel before newpnlFill, it will not be the 4th Control in the Panel anymore, so you need to change you access code.
My advice is to create your own UserControl that inherit from TabPage, in this way you can create private variables that will always reference to the Panels you want to change. Moreover, the btnAddPrgm1_Click event will be much more clear, moving the build of the page in your class constructor.
Something like:
Private Sub btnAddPrgm1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAddPrgm1.Click
Dim newTab As New MyTabPage()
pageindx = (TabControl1.TabPages.Count + 1)
newTab.Text = "Step " & pageindx
TabControl1.TabPages.Add(newTab)
End Sub