How to customize the click event of dynamically created buttons from string array? - vb.net

Here is the code I have:
Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddNewButton()
End Sub
Public Sub AddNewButton()
Dim buttonTop As Integer = 100
For Each item As String In Globals.candidates
Dim btn As New System.Windows.Forms.Button()
Dim Location As New Point(100, (buttonTop + 20))
btn.Location = Location
btn.Text = item
btn.Width = 150
AddHandler btn.Click, AddressOf Me.buttonClick
Me.Controls.Add(btn)
buttonTop += 20
Next
End Sub
Private Sub buttonClick()
Dim result As Integer = MessageBox.Show(String.Format("Did you select {0} ?", ???????????), "Confirmation", MessageBoxButtons.YesNo)
If result = DialogResult.Yes Then
MessageBox.Show("Yes pressed")
Else
MessageBox.Show("No pressed")
End If
End Sub
End Class
Globals.candidates is a global string array variable that holds a name "LastName, FirstName" and when the form is loaded I call the AddNewButton() Sub and it creates buttons for each item in the string array. No problem.
If you see in my code the "??????????" section, I don't know how to reference the dynamically created buttons's text so that I can display the proper "Did you select thisButton.text" properly.
Any help is appreciated.
Thanks!
EDIT:
Code changed as per suggestions: (Working)
Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddNewButton()
End Sub
Public Sub AddNewButton()
Dim buttonTop As Integer = 100
For Each item As String In Globals.candidates
Dim btn As New System.Windows.Forms.Button()
Dim Location As New Point(100, (buttonTop + 20))
btn.Location = Location
btn.Text = item
btn.Width = 150
AddHandler btn.Click, AddressOf Me.buttonClick
Me.Controls.Add(btn)
buttonTop += 20
Next
End Sub
Private Sub buttonClick(sender As Object, e As EventArgs)
Dim btn As Button = DirectCast(sender, System.Windows.Forms.Button)
Dim result As Integer = MessageBox.Show(String.Format("Did you select {0} ?", btn.Text), "Confirmation", MessageBoxButtons.YesNo)
If result = DialogResult.Yes Then
MessageBox.Show("Yes pressed")
Else
MessageBox.Show("No pressed")
End If
End Sub
End Class

You need to put the proper signature on your event handler:
Private Sub buttonClick(sender As Object, e As EventArgs)
Then, you can use the sender object (which will be whichever button was clicked)
Dim button As Button = DirectCast(sender, System.Windows.Forms.Button)
Dim result As Integer = MessageBox.Show(String.Format("Did you select {0} ?", button.Text), "Confirmation", MessageBoxButtons.YesNo)

To get a reference to the button clicked you need to declare the event handler of the button click with the two parameters that are passed to it by the form engine.
Private Sub buttonClick(sender as Object, e as EventArgs)
Now, this correct event handler receives a parameter named sender that is the control reference to the button clicked. You could cast it to a button and then extract the Text property
Private Sub buttonClick(sender as Object, e as EventArgs)
Dim btn = DirectCast(sender, System.Windows.Forms.Button)
if btn IsNot Nothing then
Dim result As Integer = MessageBox.Show(String.Format("Did you select {0} ?", btn.Text), "Confirmation", MessageBoxButtons.YesNo)
If result = DialogResult.Yes Then
MessageBox.Show("Yes pressed")
Else
MessageBox.Show("No pressed")
End If
End If
End Sub
This should be enough in this simple case where you have just a string data, but, if you need to associate a more complex object (like an instance of a Person class for example) you could use the Tag property of every dynamically added button to store there a reference to the instance of the class
As a side note, your code works also without the declaration of the two parameters because you have the Option Strict configuration set to OFF. This is a bad practice because it introduces subtle errors in you parameters usage and in automatic conversions of type. If you are just starting with a new project remember to set its property Option Strict to ON

Related

VB fires changetext event before control is loaded

I need to add some controls to a Visual Basic 2017 form programmatically. One of the controls is a textbox that needs a changetext event handler. Below is some code that accomplishes that task.
HOWEVER, the changetext event handler seems to fire right away, before the form even loads... before the textbox itself even loads! A "click" handler works fine, as expected. But changetext? Nope.
I've thrown together a simplified version to demonstrate. The line with the "DIES RIGHT HERE" comment causes the problem (not the comment, but the code to the left of it).
A textbox that is added at design time will work fine, not cause this problem, but that isn't an option.
What's causing this the changetext handler to be run early? How do I work around this?
Public Class Form1
Dim txtTest As TextBox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim pntTextBox As Point
pntTextBox.X = 100
pntTextBox.Y = 100
txtTest = New TextBox
With txtTest
.Location = pntTextBox
.Width = 100
AddHandler txtTest.TextChanged, AddressOf txtTest_TextChanged
End With
Me.Controls.Add(txtTest)
End Sub
Private Sub txtTest_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyClass.TextChanged
Dim strTest As String
strTest = Str(txtTest.Width) ' ****** DIES RIGHT HERE
MsgBox(strTest)
End Sub
End Class
Made a few changes. Works.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For x As Integer = 1 To 2 'create multiple TB's
Dim pntTextBox As Point
pntTextBox.X = 100 * x
pntTextBox.Y = 100
Dim txtTest As TextBox = New TextBox
With txtTest
txtTest.Name = "tb_" & x.ToString
AddHandler txtTest.TextChanged, AddressOf txtTest_TextChanged
.Location = pntTextBox
.Width = 100
End With
Me.Controls.Add(txtTest)
Next
End Sub
Private Sub txtTest_TextChanged(ByVal sender As Object,
ByVal e As System.EventArgs) 'no handler at design time
Dim tb As TextBox = DirectCast(sender, TextBox)
Dim strTest As String
strTest = tb.TextLength.ToString
Debug.WriteLine("{0} {1}", tb.Name, strTest) 'put breakpoint here
End Sub
End Class

Underlines of MaskedTextBox disappear

i have a problem in Windows Forms. I've created a form where I can enter the first and last name and click on the Search-Button to show another form with the following code:
Private Sub btnSearchUser_Click(sender As Object, e As EventArgs) Handles btnSearchUser.Click
If Me._clsfrmChild Is Nothing Then
Me._clsfrmChild = New clsFrmChild
End If
If Me._clsfrmChild.ShowDialog = False Then
Me._clsfrmChild.ShowDialog(Me)
End If
In the second form I have a MaskedTextbox:
Empty MaskedTextBox
Whatever I do, If I close the second form with Visible = False and reopen it again, the MaskedTextBox looks like this:
MaskedTextBox without underlines
I close the second form that way:
Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
Me.Visible = False
End Sub
Does someone of you know why this problem is caused?
This is really not the way you supposed to work with dialogs. The proper way is this
dim result as DialogResult
using f as Form = New MyForm()
result = f.ShowDialog()
' here you can work with form's public properties etc
end using
' optionally here you can continue massaging the result
if result = DialogResult.Ok then
' do for ok result
else
' do for other result. You can have severul results - add elseif
end if
Here is how to make a dialog with results in general. This will be similar to how MessageBox.Show() works.
Public Class clsFrmChild
Private Sub New()
InitializeComponent()
End Sub
Public Shared Shadows Function Show() As DialogResult
Dim result As DialogResult
Using f As New clsFrmChild()
result = f.ShowDialog()
End Using
Return result
End Function
Private Sub OkButton_Click(sender As Object, e As EventArgs) Handles OkButton.Click
DialogResult = DialogResult.OK
Close()
End Sub
Private Sub CancelButton_Click(sender As Object, e As EventArgs) Handles CancelButton.Click
DialogResult = DialogResult.Cancel
Close()
End Sub
End Class
Note the constructor is privatized so there is no more Me._clsfrmChild = New clsFrmChild. You will see the displaying of the modal dialog is much simpler when called like MessageBox
Private Sub btnSearchUser_Click(sender As Object, e As EventArgs) Handles btnSearchUser.Click
Dim result = clsFrmChild.Show() ' static method call instead of instance method
Select Case result
Case DialogResult.OK
MessageBox.Show("Ok")
Case DialogResult.Cancel
MessageBox.Show("Cancel")
End Select
End Sub
If you are not interested in returning a standard DialogResult, you could change the return type to whatever you like such as a custom class, with more information (such as you want to return a string in addition to DialogResult) i.e.
Public Class clsFrmChildResult
Public Property Text As String
Public Property DialogResult As DialogResult
End Class
...
Public Shared Shadows Function Show() As clsFrmChildResult
Dim result As clsFrmChildResult
Using f As New clsFrmChild()
Dim dr = f.ShowDialog()
result = New clsFrmChildResult With {.Text = TextBox1.Text, .DialogResult = dr}
End Using
Return result
End Function
...
Private Sub btnSearchUser_Click(sender As Object, e As EventArgs) Handles btnSearchUser.Click
Dim result = clsFrmChild.Show()
Select Case result.DialogResult
Case DialogResult.OK
MessageBox.Show("Ok")
Dim myString = result.Text
Case DialogResult.Cancel
MessageBox.Show("Cancel")
End Select
End Sub

simple dialog like msgbox with custom buttons (vb)

I want to ask user for example "Do you want to go right or left?".
To have simple Code I use MSGBOX with a prompt like:
"Do you want to go right or left"
Press "YES for 'right' / NO for 'left'"
Then I process Yes/No/Cancel that was pressed. This works but is ugly and in some cases hard to understand.
Also in Addition in some cases I have more than 2 choices - but that is probable another question...
You can create one dynamically
Public Module CustomMessageBox
Private result As String
Public Function Show(options As IEnumerable(Of String), Optional message As String = "", Optional title As String = "") As String
result = "Cancel"
Dim myForm As New Form With {.Text = title}
Dim tlp As New TableLayoutPanel With {.ColumnCount = 1, .RowCount = 2}
Dim flp As New FlowLayoutPanel()
Dim l As New Label With {.Text = message}
myForm.Controls.Add(tlp)
tlp.Dock = DockStyle.Fill
tlp.Controls.Add(l)
l.Dock = DockStyle.Fill
tlp.Controls.Add(flp)
flp.Dock = DockStyle.Fill
For Each o In options
Dim b As New Button With {.Text = o}
flp.Controls.Add(b)
AddHandler b.Click,
Sub(sender As Object, e As EventArgs)
result = DirectCast(sender, Button).Text
myForm.Close()
End Sub
Next
myForm.FormBorderStyle = FormBorderStyle.FixedDialog
myForm.Height = 100
myForm.ShowDialog()
Return result
End Function
End Module
You see you have options as to what buttons are present, the message, and title.
Use it like this
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim result = CustomMessageBox.Show(
{"Right", "Left"},
"Do you want to go right or left?",
"Confirm Direction")
MessageBox.Show(result)
End Sub
End Class
In my example, the prompt is "Do you want to go right or left?" and the options are "Right" and "Left".
The string is returned as opposed to DialogResult because now your options are unlimited (!). Experiment with the size to your suiting.
You need to create your own "custom" msgbox form according to your needs, and its better to create a reusable control - pass your "question" string via the constructor of your custom control.
You need some "way" to get the user decision from out side your custom msgbox - one way is use DialogResult Enum for that.
Here is a basic example i just wrote to demonstrate that, please see the comments i have added inside the code.
create a new project with 2 forms, Form1 will be the main form that will call the custom msgbox and Form2 will be the custom msgbox:
Form1:
Form2:
Code for Form1:
Public Class Form1
Private Sub btnOpenCustomMsgbox_Click(sender As Object, e As EventArgs) Handles btnOpenCustomMsgbox.Click
Dim customMsgbox = New Form2("this is my custom msg, if you press yes i will do something if you press no i will do nothing")
If customMsgbox.ShowDialog() = DialogResult.Yes Then
' do something
MsgBox("I am doing some operation...")
Else
' do nothing (its DialogResult.no)
MsgBox("I am doing nothing...")
End If
End Sub
End Class
Code for Form2:
Public Class Form2
' field that will contain the messege
Private PromtMsg As String
Sub New(ByVal promtmsg As String)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
' set global field with the argument that was passed to the constructor
Me.PromtMsg = promtmsg
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' set the msg label
Me.lblPromtMsg.Text = Me.PromtMsg
End Sub
Private Sub btnCustomYes_Click(sender As Object, e As EventArgs) Handles btnCustomYes.Click
' user choosed yes - return DialogResult.Yes
Me.DialogResult = DialogResult.Yes
Me.Close()
End Sub
Private Sub btnCustomNo_Click(sender As Object, e As EventArgs) Handles btnCustomNo.Click
' user choosed no - DialogResult.no
Me.DialogResult = DialogResult.No
Me.Close()
End Sub
End Class
it can be much more sophisticated but if you explore that example i hope you will understand the general idea.

ContextMenuStrip selected item lost in UserControl

I have a VB.NET User Control which is embedded into another User Control and that into a form. The inner User Control has a contextmenustrip triggered by a DataGridView row click. This successfully activates the event handler (I see the "OK" message), but the sender does not send the selected item (I don't see the other MsgBox messages). Here is the code:
Public CMSV As ContextMenuStrip
Private grdSourceViewerCurrentRow As Long
Public Sub grdSourceViewer_RowHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles grdSourceViewer.RowHeaderMouseClick
'code to review/edit source details
Select Case e.Button
Case Windows.Forms.MouseButtons.Right
grdSourceViewerCurrentRow = e.RowIndex 'retain for downstream code
CMSV = New ContextMenuStrip
AddHandler CMSV.MouseClick, AddressOf SourceViewDocumentationEdit
CMSV.Items.Add("Edit")
CMSV.Items.Add("Transfer to Evidence")
Dim Pt As Point = New Point()
Pt.X = grdSourceViewer.PointToClient(Cursor.Position).X
Pt.Y = grdSourceViewer.PointToClient(Cursor.Position).Y + 20
CMSV.Show(sender, Pt)
Case Windows.Forms.MouseButtons.Left
Exit Sub
Case Else
Exit Sub
End Select
End Sub
Public Sub SourceViewDocumentationEdit()
MsgBox("OK") 'I can see it reaches here
Dim cc As ToolStripItemCollection = CMSV.Items
Dim SelectedItem As Integer = -1
Dim SelectedValue As String = ""
For i As Integer = 0 To cc.Count - 1
If cc.Item(i).Selected Then
SelectedItem = i
SelectedValue = cc.Item(SelectedItem).Text
Exit For
End If
Next
Select Case SelectedValue
Case "Edit"
MsgBox("Edit code here")
Case "Transfer to Evidence"
MsgBox("Transfer code here")
End Select
End Sub
What is wrong here? Why am I losing the info about the item that was clicked?
Why are you recreating the menu each time?
At any rate, store the ToolStripMenuItem returned by CMSV.Items.Add() and wire that up instead.
Simplified example:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CMSV = New ContextMenuStrip
Dim TSMI As ToolStripMenuItem
TSMI = CMSV.Items.Add("Edit")
AddHandler TSMI.Click, AddressOf TSMI_Click
TSMI = CMSV.Items.Add("Transfer to Evidence")
AddHandler TSMI.Click, AddressOf TSMI_Click
' ...
End Sub
Private Sub TSMI_Click(sender As Object, e As EventArgs)
Dim TSMI As ToolStripMenuItem = DirectCast(sender, toolstripmenuitem)
Select Case TSMI.Text
Case "Edit"
Debug.Print("...Edit Code...")
' use "grdSourceViewerCurrentRow " in here?
Case "Transfer to Evidence"
Debug.Print("...Transfer to Evidence Code...")
' use "grdSourceViewerCurrentRow " in here?
End Select
End Sub
You need to use a ToolStripMenuItem and not just a string. Then you can add the handler for it's click event.
Dim tsmi As New ToolStripMenuItem
tsmi.Text = "Edit"
AddHandler tsmi.Click, AddressOf ItemClicked
CMSV.Items.Add(tsmi)
Then the event sub:
Private Sub ItemClicked(sender As Object, e As EventArgs)
'item clicked
'sender object would be the ToolStripMenuItem
End Sub

How to return selected item on listbox on the second form to the main form in visual basic?

So basically you have a main form that shows only a label (lblTotalPrice) and a button that when you click it, it opens a second form that contains a listbox with different prices packages (lstPackages). How do do you select an item from lstPackages that is on the second form and return it to lblTotalPrice, which is located on the main form?
I attempted this code using a function (thought it would be useful for calculations), but it seems like it didn't work:
On my second form, I populated the lstPackages with it's name using parallel arrays:
Dim strPackages As String = {"Package 1", "Package 2", "Package 3", "Package 4", "Package 5"}
Dim decPrice As String = {100D, 200D, 300D, 400D, 500D}
Private Sub SecondForm_Activated(sender As Object, e As EventArgs) Handles MyBase.Activate
Dim frmMain as New
Dim i As Integer
For i = 0 To strPackages.Length - 1
lstPackages.Items.Add(strPackages(i))
Next
End Sub
After I tried to select a value on lstPackages, but I just was not too sure if I was using the right code to select the item and return it to the main form:
Function CalcPackage()
Dim decPackages As Decimal = 0D
If lstPackages.SelectedIndex = -1 Then
MessageBox.Show("Select a package")
Else
For i = 0 To lstPackages.SelectedItem - 1
decPackages = lstPackages.SelectedItem(i)
Next i
End If
Return decPackages
End Function
When I have selected the item in the lstPackages, I tried to send the selected item back to the main form, but ran into a problem here:
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnClose.Click
Dim frmMainForm As New MainForm
Dim decTotal As Decimal
decTotal = CalcPackage()
'I was stuck in this part and wasn't sure how to return it to lblTotalPrice (and also close the second form too)
frmMainForm.lblTotalPrice.Text = decTotal.ToString("c")
frmMainForm.Show()
Me.Close()
End Sub
So yea sorry if my code is weird, but I'm hoping to get help on how to return the value of the item to lblTotalPrice on the main form, thanks guys
Part of your problem is that you are newing up an instance of the main form in the button exit click event handler of the second form, as shown in this code posted:
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnClose.Click
Dim frmMainForm As New MainForm
Dim decTotal As Decimal
decTotal = CalcPackage()
'I was stuck in this part and wasn't sure how to return it to lblTotalPrice (and also close the second form too)
frmMainForm.lblTotalPrice.Text = decTotal.ToString("c")
frmMainForm.Show()
Me.Close()
End Sub
Instead pass an instance of the main form to the second form via its constructor, like this:
Public Class SecondForm Inherits Form
Dim theMainForm As MainForm
Public Sub New(mainForm As MainForm)
theMainForm = mainForm
End Sub
End Class
Now you can reference the lblTotalPrice in the MainForm class via the theMainForm variable, like this:
theMainForm.lblTotalPrice.Text = lstPackages.SelectedItem
The theMainForm.lblTotalPrice.Text should only be updated in response to an item being selected from lstPackages not when the second form is closed; so handle the updating of the main form's label in the SelectedIndexChanged event of lstPackages, like this:
Protected Sub lstPackages_IndexChanged(sender As Object, e As EventArgs)
Dim decTotal As Decimal
decTotal = CalcPackage()
theMainForm.lblTotalPrice.Text = decTotal.ToString("c")
End Sub
All the exit button click event handler should do is to show the main form and close the second form, like this:
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnClose.Click
theMainForm.Show()
Me.Close()
End Sub
Just display SecondForm with ShowDialog(), then retrieve the value with the reference to it that you already have:
Public Class MainForm
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim sf As New SecondForm
If sf.ShowDialog = Windows.Forms.DialogResult.OK Then
lblTotalPrice.Text = sf.TotalPrice
End If
End Sub
End Class
Over in SecondForm:
Public Class SecondForm
Private _TotalPrice As Decimal
Public ReadOnly Property TotalPrice As Decimal
Get
Return _TotalPrice
End Get
End Property
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnClose.Click
If lstPackages.SelectedIndex = -1 Then
MessageBox.Show("Select a package")
Else
_TotalPrice = lstPackages.SelectedItem(0)
Me.DialogResult = Windows.Forms.DialogResult.OK
End If
End Sub
End Class