How do I associate multiple user input fields using a for/next loop? - vb.net

VB.NET 2008 windows app form.
I have a groupbox with several checkboxes, comboboxes, and textboxes within it.
With the help of StackOverFlow members, I've learned how to use the FOR/NEXT loop to find all checkboxes that are checked.
Code:
Dim chk As CheckBox
Dim sb As New System.Text.StringBuilder
For Each chk In gbInterior.Controls.OfType(Of CheckBox)()
If chk.Checked Then
sb.AppendLine(chk.Text)
End If
Next chk
I am then using this information to write the checkbox names in the body of an email, using the following code:
Dim Outl As Object
Outl = CreateObject("Outlook.Application")
If Outl IsNot Nothing Then
Dim omsg As Object
omsg = Outl.CreateItem(0)
omsg.To = ""
omsg.Subject = "Cabinet Request"
omsg.Body = "A cabinet request has been created with the following information:" _
+ vbCrLf + sb.ToString
As you can see by the code sb.tostring will print every checked checkbox name on its own line...great.
How do I associate the proper combobox.selecteditem, textbox.value, and checkbox name to print out on the same line.
When I say proper...example; checkbox=pen, combobox=color, textbox=quantity.Let's say my other checkboxes are different items and the rest of the boxes are the same. Since I'm using a loop, how do I create the association? This is the result I'm looking for:
pen blue 1
pencil black 3
eraser pink 2
Thanks in advance

Final EDIT (tested and confirmed):
Public Class Form1
Private Enum interiorTypes
Rack
RackShelf
RackSlide
RackDrawer
BackPanel
Shelf
Drawer
Light
Fan
Therm
End Enum
Private Sub btnOrder_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOrder.Click
Dim sb As New System.Text.StringBuilder
For Each type As interiorTypes In System.Enum.GetValues(GetType(interiorTypes))
Dim chk = CType(gbInterior.Controls.Find("c" & type.ToString(), True)(0), CheckBox)
Dim cb = CType(gbInterior.Controls.Find("cb" & type.ToString(), True)(0), ComboBox)
Dim tb = CType(gbInterior.Controls.Find("tb" & type.ToString(), True)(0), TextBox)
If chk.Checked Then
sb.AppendFormat("{0} {1} {2}", chk.Text, cb.SelectedItem.ToString, tb.Text)
sb.AppendLine()
End If
Next
MsgBox(sb.ToString, MsgBoxStyle.OkOnly, "Order Summary")
End Sub
End Class
If you don't have all 3 inputs for each Interior Type, then you will need to check for Nothingness. To go with your example of a missing ComboBox, after your Dim declarations inside your For loop, you could do:
If chk.Checked Then
sb.Append(chk.Text)
If cb IsNot Nothing Then sb.Append(" " & cb.SelectedItem.ToString)
sb.Append(" " & tb.Text)
sb.AppendLine()
End If

If all checkboxes, text boxes and comboboxes are part of a single parent (eg. the group box) it's nearly impossible to link them together.
I think you should look at grouping each set of items within a panel:
+- Panel 1 -----------------------------------------+
| Check box 1-------- Text Box 2 ---- Combobox 1 ---|
+---------------------------------------------------+
+- Panel 2 -----------------------------------------+
| Check box 2-------- Text Box 2 ---- Combobox 2 ---|
+---------------------------------------------------+
You can then loop through all panels in the group, and for each panel loop through all the controls within:
Dim stringbld As New System.Text.StringBuilder
Dim fullstring As String
For Each pnl As Panel In gbInterior.Controls.OfType(Of Panel)()
Dim pen As Boolean
Dim color As String = ""
Dim quantity As Integer
For Each ctrl As Control In pnl.Controls
If TypeOf (ctrl) Is CheckBox Then
Dim tmpchk As CheckBox = ctrl
pen = tmpchk.Checked
End If
If TypeOf (ctrl) Is ComboBox Then
Dim tmpchk As ComboBox = ctrl
color = tmpchk.SelectedValue
End If
If TypeOf (ctrl) Is TextBox Then
Dim tmpchk As TextBox = ctrl
If IsNumeric(tmpchk.Text) Then
quantity = CInt(tmpchk.Text)
End If
End If
Next
stringbld.AppendLine(pen & " " & color & " " & quantity)
Next
fullstring = stringbld.ToString
An alternative might be to look at using a data repeater, this has methods for getting all values:
This stackoverflow thread

Related

Remove specific row of TableLayoutPanel using dynamically created button of each row

So I am making a function that will populate the TableLayoutPanel from FileDialog Result then make a delete button for each row using a loop. Here's the code
Private PathtoFile1 As New List(Of String) 'this will contain all the selected file in the dialogwindow
Private rowLineDrawing As Integer = 0
Private selectedfilecountLineDrawing As Integer
Public Function AttachFileLineDrawing(TLP As TableLayoutPanel)
Dim dr = OpenFileDialog1.ShowDialog
If (dr = System.Windows.Forms.DialogResult.OK) Then
selectedfilecountLineDrawing = OpenFileDialog1.FileNames.Count
For Each FileName In OpenFileDialog1.FileNames
Try
Console.WriteLine(FileName.ToString)
PathtoFile1.Add(FileName.ToString)
Catch SecEx As Security.SecurityException
MessageBox.Show("Security error. Please contact your administrator for details.\n\n" &
"Error message: " & SecEx.Message & "\n\n" &
"Details (send to Support):\n\n" & SecEx.StackTrace)
Catch ex As Exception
'Could Not Load the image - probably permissions-related.
MessageBox.Show(("Cannot display the image: " & FileName.Substring(FileName.LastIndexOf("\"c)) &
". You may not have permission to read the file, or " + "it may be corrupt." _
& ControlChars.Lf & ControlChars.Lf & "Reported error: " & ex.Message))
End Try
Next
'MAKE SOMETHING HERE TO DISPLAY THE SELECTED ITEMS IN THE TABLELAYOUTPANEL OF THE SUBMIT PROGRESS
TLP.Controls.Clear()
TLP.RowCount = 0
rowLineDrawing = 0
For Each Path In PathtoFile1
Dim filepath As New Label
filepath.Text = Path
filepath.Width = Val(360)
'this button is for previewing the file
Dim btnPreview As New Button
AddHandler btnPreview.Click,
Sub(s As Object, e As EventArgs)
Dim btn = CType(s, Button)
MsgBox("This is Preview")
End Sub
'This button is for removing rows in the tablelayoutpanel
Dim btnRmv As New Button
Dim StringToIndex As String = Path 'THIS CATCHES EVERY PATH IN THE LOOP AND STORE IT TO THE VARIABLE WHICH THEN BE USED AS A COMPARABLE PARAMETER FOR THE INDEX SEARCH
Dim index = PathtoFile1.IndexOf(Path)
AddHandler btnRmv.Click,
Sub(s As Object, e As EventArgs)
Dim btn = CType(s, Button)
MsgBox(index)
PathtoFile1.RemoveAt(index) 'THIS LINE OF CODE REMOVE THE SPECIFIC ITEM IN THE LIST USING THE BTNRMV CLICK
'MAKE SOMETHING HERE TO REMOVE THE ROW IN THE TABLELAYOUTAPANEL
End Sub
TLP.SuspendLayout()
TLP.RowStyles.Add(New RowStyle(SizeType.Absolute, 20))
TLP.Controls.Add(filepath, 0, rowLineDrawing)
TLP.Controls.Add(btnPreview, 1, rowLineDrawing)
TLP.Controls.Add(btnRmv, 2, rowLineDrawing)
TLP.ResumeLayout()
rowLineDrawing -= -1
Next
End If
End Function
So I am trying to remove the row in the TableLayoutPanel together with the dynamic control. My approach is removing the selected item in the list and I achieved it properly but can't remove the row in the TableLayoutPanel. Any help is much appreciated!
EDIT
I have tried to use the provided module above but got this error
And got this error
Here is an extension method that will enable you to remove any row from a TableLayoutPanel by index:
Imports System.Runtime.CompilerServices
Public Module TableLayoutPanelExtensions
<Extension>
Public Sub RemoveRowAt(source As TableLayoutPanel, index As Integer)
If index >= source.RowCount Then
Throw New ArgumentOutOfRangeException(NameOf(index),
index,
"The row index must be less than the number of rows in the TableLayoutPanel control.")
End If
'Remove the controls in the specified row.
For columnIndex = 0 To source.ColumnCount - 1
Dim child = source.GetControlFromPosition(columnIndex, index)
If child IsNot Nothing Then
child.Dispose()
End If
Next
'Move controls below the specified row up.
For rowIndex = index + 1 To source.RowCount - 1
For columnIndex = 0 To source.ColumnCount - 1
Dim child = source.GetControlFromPosition(columnIndex, rowIndex)
If child IsNot Nothing Then
source.SetCellPosition(child, New TableLayoutPanelCellPosition(columnIndex, rowIndex - 1))
End If
Next
Next
'Remove the last row.
source.RowCount -= 1
End Sub
End Module
I tested that on 3 column by 4 row TableLayoutPanel containing a Label in each cell executing the following code twice:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TableLayoutPanel1.RemoveRowAt(1)
End Sub
The result was as expected, i.e. removing the second-from-top row each time. You may need to fiddle a bit more depending on what you want to happen row heights. I had the row heights set to equal percentages so the remaining rows grew proportionally to fill the space. If you want something different, you can add code accordingly. Note that you could create an almost identical method for removing columns.

Preview different text when CheckBoxes are changed

I did not find any solution to my problem, perhaps it is because I cannot express correctly what I really want to do.
Basically, I have 4 CheckBoxes and when some of them are checked, I want to add some text to a TextBox with multiple lines.
Example:
If checkbox1.checked = true then
Textbox1.text = text1
Elseif checkbox1.checked = true and checkbox2.checked = true then
Textbox1.text = text1 & vbCrLf & text2
Elseif checkbox1.checked = true and checkbox2.checked = true and checkbox3.checked = true then
Textbox1.text = text1 & vbCrLf & text2 & vbCrLf & text3
Elseif checkbox1.checked = true and checkbox3.checked = true then
textbox1.text = text1 & vbCrLf & text3
End if
I know there is a logical error in the code (If i have 4 CheckBoxes checked it will show only text1 rather than text1 & vbCrLf & text2 & vbCrLf & text3 & vbCrLf & text4) but I do not know how to express my problem in another way.
If I check the first and the third CheckBoxes I want to see text1 and text3 accordingly, do I have to hard code every single possible way I could check the CheckBoxes or is there a way to automatically add the text I need on a new line?
What I would suggest is that you start by assigning the text that corresponds to each CheckBox to the Tag property of that CheckBox. You can do that in the designer. You can then just get a list of checked boxes, get the text for each one and join them together, e.g.
Dim checkBoxes = {CheckBox1, CheckBox2, CheckBox3, CheckBox4}
TextBox1.Text = String.Join(Environment.NewLine,
checkBoxes.Where(Function(cb) cb.Checked).
Select(Function(cb) CStr(cb.Tag)))
That creates a list of all CheckBoxes, filters it to only the checked ones, gets the text for each of those and then joins those substrings into a single String using line breaks as delimiters. For those who prefer LINQ query syntax to function syntax:
Dim checkBoxes = {CheckBox1, CheckBox2, CheckBox3, CheckBox4}
TextBox1.Text = String.Join(Environment.NewLine,
From cb In checkBoxes
Where cb.Checked
Select CStr(cb.Tag))
If you don't want to go the LINQ route then you can go with a more conventional loop:
Dim checkBoxes = {CheckBox1, CheckBox2, CheckBox3, CheckBox4}
Dim substrings As New List(Of String)
For Each cb In checkBoxes
If cb.Checked Then
substrings.Add(CStr(cb.Tag))
End If
Next
TextBox1.Text = String.Join(Environment.NewLine, substrings)
Don't use "else if". Each checkbox should be in an "if....end if" by itself.
Textbox1.Text = ""
FinalText = ""
If checkbox1.checked = true then
FinalText = text1 & vbCrLf
End If
If checkbox2.checked = true then
FinalText = FinalText & text2 & vbCrLf
End If
...
Textbox1.Text = FinalText
etc.
Another method that can handle any number of CheckBoxes as source and any kind of Control as Output (provided that the Output Control has a Text property).
Build a list of the strings used to compose the Output text.
Declare it:
Private ChechBoxesText As List(Of String) = New List(Of String)
then fill it, when appropriate:
(the source of the strings can be anything that can be converted to/viewed as an Array(Of String))
ChechBoxesText.AddRange({"Text1", "Text2", "Text3", "Text4", "Text5"})
Add a common handler for the CheckedChanged event to all the CheckBoxes:
For Each ctl As CheckBox In Controls.OfType(Of CheckBox).ToList
AddHandler ctl.CheckedChanged, AddressOf CheckBoxes_CheckedChanged
Next
Note:
The CheckBox controls could also be located inside another container (GroupBox, Panel etc.). In this case, the collection considered will be (when the container is a GroupBox):
Controls("GroupBox1").Controls.OfType(Of CheckBox)
Define the Handler, which will call a Function specifying the Control that will receive the Output:
Here it's a TextBox control, but it could also be a RichTextBox, a Label, a Button (...).
Private Sub CheckBoxes_CheckedChanged(sender As Object, e As EventArgs)
RebuilText(TextBox1)
End Sub
Private Sub RebuilText(OutPut As Control)
OutPut.Text = String.Join(Environment.NewLine, Controls.OfType(Of CheckBox).
Where(Function(c) c.Checked = True).
OrderBy(Function(c) c.Name.Length).
ThenBy(Function(c) c.Name).
Select(Function(c) ChechBoxesText(CInt(c.Name.Substring("CheckBox".Length)) - 1)))
End Sub
A description of the LINQ methods used:
Using the current Form's Collection of Controls of Type CheckBox as source:
Controls.OfType(Of CheckBox)
Where() filters the Collection, considering only Controls of Type CheckBox that are currently Checked:
Where(Function(c) c.Checked = True)
c is one of the elements of the source Collection. A CheckBox control, in this case.
OrderBy() Orders the Collection by Control.Name.Length first, then by the Control.Name string.
This will order the Controls by their Name and the trailing number (CheckBox1, CheckBox2, CheckBox11).
If we don't order by Name.Lenght first, CheckBox11 will appear before CheckBox2 in the sort order.
OrderBy(Function(c) c.Name.Length).
ThenBy(Function(c) c.Name).
Select() is a transformation method. It allows to return a different type or value.
In this case, it will return the string contained in the ChechBoxesText List(Of String).
The List(Index) position is extracted from the CheckBox.Name trailing number, converted to an Integer:
Select(Function(c) ChechBoxesText(CInt(c.Name.Substring("CheckBox".Length)) - 1)))
Here, the Substring(StartIndex) method will return a new String from the CheckBox.Name, starting from the "CheckBox".Length position to CheckBox.Name.Length.
You could also use a CheckBox.Tag property to store the Index of the related Text in the ChechBoxesText List.
In this case, the Select() method would be:
Select(Function(c) ChechBoxesText(Convert.ToInt32(c.Tag))))

What is the best way to loop this program?

This is one of the form, all the usercontrol value in this form will store in My.Settings
I have another form with a FlowLayoutPanel, everytime when the application start,
if Active checked then it will add a Button with discount value to the FlowLayoutPanel.
Should I add those usercontrol to a list and then loop through the list? Or what is the best way to solve this kind of problem?
UPDATED
How can I add multiple item to list in 1 code? I getting this error when system run to line 5
An exception of type 'System.NullReferenceException' occurred in XXX.exe but was not handled in user code
Additional information: Object reference not set to an instance of an object.
Public Sub RefreshDiscount(ByRef ref As scr_mainDiscount)
Dim li_disName As New List(Of TextBox)
Dim li_disValue As New List(Of TextBox)
Dim li_disType As New List(Of ComboBox)
Dim li_active As New List(Of CheckBox)
Dim tb_disName As TextBox() = {ref.tb_name1, ref.tb_name2, ref.tb_name3, ref.tb_name4, ref.tb_name5, ref.tb_name6, ref.tb_name7, ref.tb_name8, ref.tb_name9, ref.tb_name10}
Dim tb_disValue As TextBox() = {ref.tb_value1, ref.tb_value2, ref.tb_value3, ref.tb_value4, ref.tb_value5, ref.tb_value6, ref.tb_value7, ref.tb_value8, ref.tb_value9, ref.tb_value10}
Dim cb_disType As ComboBox() = {ref.cb_type1, ref.cb_type2, ref.cb_type3, ref.cb_type4, ref.cb_type5, ref.cb_type6, ref.cb_type7, ref.cb_type8, ref.cb_type9, ref.cb_type10}
Dim chkb_active As CheckBox() = {ref.CheckBox1, ref.CheckBox2, ref.CheckBox3, ref.CheckBox4, ref.CheckBox5, ref.CheckBox6, ref.CheckBox7, ref.CheckBox8, ref.CheckBox9, ref.CheckBox10}
li_disName.AddRange(tb_disName)
li_disValue.AddRange(tb_disValue)
li_disType.AddRange(cb_disType)
li_active.AddRange(chkb_active)
For index As Integer = 0 To li_active.Count - 1
If li_active(index).Checked = False Then
li_disName.RemoveAt(index)
li_disValue.RemoveAt(index)
li_disType.RemoveAt(index)
li_active.RemoveAt(index)
Else
Dim btn As New ctrl_DiscountButton
With btn
.Text = li_disName(index).Text
.Price = li_disValue(index).Text
.Type = li_disType(index).Text
End With
scr_sales.flp_discount.Controls.Add(btn)
End If
Next
li_disName.Clear()
li_disValue.Clear()
li_disType.Clear()
li_active.Clear()
End Sub
Here's a simple example showing how to find CheckBox1 thru CheckBox10, "by name", using the "searchAllChildren" option of Controls.Find():
For i As Integer = 1 To 10
Dim ctlName As String = "CheckBox" & i
Dim matches() As Control = Me.Controls.Find(ctlName, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is CheckBox Then
Dim cb As CheckBox = DirectCast(matches(0), CheckBox)
' do something with "cb"
If cb.Checked Then
' ... code ...
' possibly use code just like this to find the matching discount value control?
End If
End If
Next

VB.Net Loop controls by names using variables

I need to set text of some controls.
I have a Form with some CheckBoxes an some TextBoxes.
In VBA (If I have 5 TextBoxes named "TextBox1", "TextBox2", ... "TextBox5") I can use something like this:
For n = 1 To 5
Me("TextBox" & n).Text = NeededValue
Next n
I know that something like this is also possible in VB.Net but I wasn't able to find the right syntax (and I didn't find similar codes on SO).
I've tryed using
Me.Controls()
But I can't insert control name this way
Me.Controls.Find("TextBox" & n, True)
would be the similar approach to your VBA Style.
Use For Each and then test with TypeOf to find all TextBoxes in your form like :
For Each myObject1 As [Object] In Me.Controls
If TypeOf myObject1 Is TextBox Then
TryCast(myObject1, TextBox).Text = "NeededValue"
End If
Next
Also :
Dim myText = {TextBox1, TextBox2, TextBox3, TextBox4, TextBox5}
For Each btn In myText
btn.Text = "NeededValue"
Next
For i As Int32 = 1 To 5
Dim Txt = Me.Controls.Find("TextBox" & i, True)
If Txt.Length > 0 Then
Txt(0).Text = "blah"
End If
Next
Or :
For i As Int32 = 1 To 5
Dim Txt = Me.Controls.Find("TextBox" & i, True)
If Txt.Length > 0 Then
Txt(0).Text = "NeededValue"
End If
Next

JOIN the Strings of different textboxes and again SPLIT

I am new in vb.net, so not much familiar with all VB functions. While working on 1 Windows Application, while taking order we need to save product and its specification like weight, height, width, color, length, material etc.
since every product can have different specification so its is not possible to determine and provide for fields in database.
So, I decided to provide textboxes so user can enter name & value while entering product details. 1 textbox for name & other textbox for value.
like this
Textbox1 = "WEIGHT" TextBox2 = "10" '(Value of Weight)
Textbox3 = "WIDTH" TextBox4 = "5" '(value of Width)
Textbox4 = "LENGTH" TextBox5 = "5" '(Value of Length)
(All these textboxes are dynamically created in Groupbox "GBox1")
Instead of saving product specification in separate column. I want to save these names & values as String e.g. "WEIGHT=10;WIDTH=5;LENGTH=5" in SQL Database(TEXT OR VARCHAR field). Because we dnt want any calculations or search etc. on this. just customer requirements to book order & save in Database for future records.
Then again While calling or editing Product SPLIT the string as separate fields, String Before = Separate & string after = separate, then display all names & their corresponding values in Textboxes (as Displayed while adding) so user can edit and after changes again save as single string value.
After search I found SPLIT & JOIN functions for this purpose.
need some help in using these functions in Loop to merge string from textboxes
for each loop to read all textboxes in Groupbox
Dim ItemList As New ArrayList()
Dim PrDetails As String
For Each Ctrl As Control In GBox1.Controls
If TypeOf Ctrl Is TextBox Then
ItemList.Add(CType(Ctrl, TextBox).Text)
End If
Next
PrDetails = String.Join()
How to perform join on these array list? and again SPLIT this pattern while retrieving from Database.
Also need suggestions regarding this approach or any other way to implement. Thanks.
For this solution to work, you will need to accept the fact that there needs to be some amount of standardization to your solution. TextBox1 and TextBox3 won't work as names, so I used txtName1 and txtValue1 etc. To test this solution, make a new form, put GroupBox1, paste the code in the class and run.
Private nameTextBoxName = "txtName" ' name textbox prefix
Private valueTextBoxName = "txtValue" ' value textbox prefix
Private paramSeparator = ";" ' between sets of parameters
Private nameAndValueSeparator = "=" ' between name and value
Private xOffset = 10 ' for horizontal spacing inside the groupbox
Private yOffset = 20 ' for vertical spacing inside the groupbox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' your example data
Dim testString = "WEIGHT=10;WIDTH=5;LENGTH=5"
' set the textboxes
setTextBoxes(testString)
' get the string from the textboxes
Dim result = getTextBoxesString()
End Sub
' call to get a string with all the data from the textboxes
Private Function getTextBoxesString() As String
Dim stringToDatabase = ""
Dim textBoxes = GroupBox1.Controls.OfType(Of Control).
Where(Function(co As Control) TypeOf co Is TextBox).
Select(Of TextBox)(Function(co As Control) CType(co, TextBox))
Dim nameTextBoxes = textBoxes.Where(Function(co As Control) co.Name.Contains(nameTextBoxName))
Dim valueTextBoxes = textBoxes.Where(Function(co As Control) co.Name.Contains(valueTextBoxName))
stringToDatabase = nameTextBoxes.Select(Of String)(
Function(nameTextBox As TextBox)
Dim valueTextBox = valueTextBoxes.
Where(Function(vtb As TextBox) vtb.Name = nameTextBox.Name.Replace(nameTextBoxName, valueTextBoxName)).
First()
Return nameTextBox.Text & nameAndValueSeparator & valueTextBox.Text
End Function).Aggregate(Function(oldValue, newValue) oldValue & paramSeparator & newValue)
Return stringToDatabase
End Function
' call to set the textboxes inside the groupbox based on the data string
Private Sub setTextBoxes(textBoxesString As String)
Dim params = textBoxesString.Split(paramSeparator)
Dim index = 1
GroupBox1.Controls.Clear()
For Each param In params
Dim nameAndValue = param.Split(nameAndValueSeparator)
Dim nameTextBox As New TextBox With
{.Name = nameTextBoxName & index.ToString(),
.Text = nameAndValue(0),
.Location = New Point(xOffset, (index - 1) * .Height + yOffset)}
Dim valueTextBox As New TextBox With
{.Name = valueTextBoxName & index.ToString(),
.Text = nameAndValue(1),
.Location = New Point(.Width + xOffset, (index - 1) * .Height + yOffset)}
GroupBox1.Controls.Add(nameTextBox)
GroupBox1.Controls.Add(valueTextBox)
index += 1
Next
End Sub