Making a List(Of ...) from multiple form object - vb.net

So I'm trying to make a list of more than one type of object, which I done a research on and found out it's, but I also researched into structures and found I can possibly make a use of that instead, but it doesn't return itself as a list.
Imports WinText = System.Windows.Forms.TextBox
Imports WinCombo = System.Windows.Forms.ComboBox
Public Class PSS
Public EntryItems = New List(Of Elements)
Structure Elements
Dim xSchool As WinCombo
Dim xClass As WinCombo
Dim xID As WinCombo
Dim xName As WinText
End Structure
End Class
Edit: So on a form I have a set of ComboBoxes and TextBoxes (about 20 more than on the example here). I want to put them into a list so that when I will be retrieving their results, or getting their names for SQL references, or anything similar, I will be able put them into a loop, thus making the code more efficient and shorter to do.

Structure has similarities to Class and is not a list.
(Read more about here)
You can create a List(Of Structure) but you need to populate it first, like djv stated already in comments.
Private Sub Populate()
Dim list As New List(Of Elements)
list.Add(New Elements)
'Add as much Elements as you want
End Sub
Assuming your Controls are placed on a Form named Form1.
For Each ctrl As Control In Form1.Controls
If TypeOf ctrl Is TextBox Then
Dim tb As TextBox = DirectCast(ctrl, TextBox)
'Do whatever you want with the current TextBox
ElseIf TypeOf ctrl Is ComboBox Then
Dim chk As ComboBox = DirectCast(ctrl, ComboBox)
'Do whatever you want with the current ComboBox
End If
Next

At first, credits to MatSnow.
Taking his concept of As Control I was able to make a List(Of Control) which allowed me to make and add items to it.
Dim ControlList As New List(Of Control)
ControlList.Add(ComboBox1)
ControlList.Add(TextBox1)
'And so on

Related

How can I use a variable to reference a textbox?

I'm new to visual basic and programming in general, but I'm trying to make a statistic counter sort of program. I'm trying to use a variable to reference a textbox, for example, k_kills(i) = txtKills(i).Text. This doesn't work, however, so I then tried the following:
For i = 0 To 8
Dim tempBox As TextBox
Dim tempName As String = "txtKills" & i.ToString
tempBox = Me.Controls.Item(tempName)
k_kills(i) = tempBox.Text
Next
This also doesn't work and spits out an error each time saying that 'tempBox was Nothing'.
Can anyone tell me if I can make this work?
Thanks.
You will need to find the control in some collection. By default the control would exist in its parent's Controls property and since you're trying to get the control by its name then you could use ControlCollection's Find method. If you can guarantee that the control's parent is the Form then you'd call:
Dim tempBox As TextBox = DirectCast(Me.Controls.Find(tempName, False), TextBox)
But if there is the possibility that the control's parent is something other than the Form then you'd call:
Dim tempBox As TextBox = DirectCast(Me.Controls.Find(tempName, True), TextBox)
The first would execute slightly quicker because it only iterates over the current ControlCollection whereas the second could take longer because if it cannot find the control in the current ControlCollection then it starts to iterate over the child controls as well.
Assuming the controls are all in Form as parent and they all start with txtKills...
If you are going to use these text boxes as a group for several actions you may want to build an array or list of TextBox.
Dim Kills(7) As TextBox
Private Sub CreateTextBoxArray()
Dim index As Integer
For Each ctrl As Control In Controls
If ctrl.Name.StartsWith("txtKills") Then
Kills(index) = DirectCast(ctrl, TextBox)
index += 1
End If
Next
End Sub
Private Sub ClearKillTextBoxes()
For Each t In Kills
t.Clear()
Next
End Sub
Private Function GetTextFromKillBoxes() As List(Of String)
Dim lst As New List(Of String)
For Each t In Kills
lst.Add(t.Text)
Next
Return lst
End Function
After Mary's comment I edit my answer to add this line --> My code does not work if Option Strict is On and 'For' starting in 0 or 1 or any number and txtKills[X] exists.
This was my previous answer and I don't know if I have to delete or not:
Your code works fine but I think you have an error because your For starts in 0 and you don't have any "txtKills0". I've tested it now:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim k_kills(10) As String '<< Ignore the length
For i = 1 To 7
Dim tempBox As TextBox
Dim tempName As String = "txtKills" & i.ToString
tempBox = Me.Controls.Item(tempName)
k_kills(i) = tempBox.Text
MsgBox(k_kills(i))
Next
End Sub

Is there any way to create a global function to clear TextBoxes?

I was wondering if there is any way to create a class with a global function/method/sub that upon
calling it will clear some of the textboxes of the form. How can i handle the different number of textboxes
each forms has?
The current code clears only the pre-defined 2 boxes. Thank you.
Public Class ClearElements
Public Sub CLEAR_TEXT(ByVal text1 As TextBox, ByVal text2 As TextBox)
text1.Clear()
text2.Clear()
End Sub
End Class
There are many ways to do it.
You can add the TextBoxes to a List, and clear each item in the list
Private ReadOnly someOfTheTextBoxes As New List(Of TextBox)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
someOfTheTextBoxes.Add(TextBox1)
someOfTheTextBoxes.Add(TextBox2)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For Each t In someOfTheTextBoxes
t.Clear()
Next
End Sub
Or make this method
Public Sub CLEAR_TEXT(textboxes As IEnumerable(Of TextBox))
For Each t In textboxes
t.Clear()
Next
End Sub
and call it with your list of TextBoxes
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
CLEAR_TEXT(someOfTheTextBoxes)
End Sub
or make an array on the spot and pass it in
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
CLEAR_TEXT({TextBox1, TextBox2})
End Sub
If you are interested in recursion at all, here are some extensions I have which could help
Module Extensions
<Runtime.CompilerServices.Extension>
Public Function ChildControls(parent As Control) As IEnumerable(Of Control)
Return ChildControls(Of Control)(parent)
End Function
<Runtime.CompilerServices.Extension>
Public Function ChildControls(Of TControl As Control)(parent As Control) As IEnumerable(Of TControl)
Dim result As New List(Of TControl)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is TControl Then result.Add(CType(ctrl, TControl))
result.AddRange(ctrl.ChildControls(Of TControl)())
Next
Return result
End Function
<Runtime.CompilerServices.Extension>
Public Function ForEach(Of TSource)(source As IEnumerable(Of TSource), action As Action(Of TSource)) As IEnumerable(Of TSource)
For Each item As TSource In source
action(item)
Next item
Return source
End Function
<Runtime.CompilerServices.Extension>
Public Function ForEach(Of TSource)(source As IEnumerable(Of TSource), action As Action(Of TSource, Integer)) As IEnumerable(Of TSource)
For i As Integer = 0 To source.Count() - 1
action(source.ElementAt(i), i)
Next
Return source
End Function
End Module
Clear all textboxes recursively
Me.ChildControls(Of TextBox).ForEach(Sub(t) t.Clear())
Or ForEach on your list
someOfTheTextBoxes.ForEach(Sub(t) t.Clear())
We have this:
You could recursively go through all the controls in the form and in case of type = Textbox clear it.
But then the plot thickens:
I've done that before. It clears all the boxes of the form. I am talking about the case where some boxes have to be untouched and some to cleared.
The solution here is two parts. First, create the recursive method as suggested like this:
Public Sub ClearText(root As Control)
For Each ctrl As Control In Root.Controls
If TypeOf ctrl Is TextBox Then ctrl.Text = String.Empty
ClearText(ctrl)
Next ctrl
End Sub
or this:
Public Sub ClearText(root As IEnumerable(Of Control))
For Each ctrl As Control In root
If TypeOf ctrl Is TextBox Then ctrl.Text = String.Empty
ClearText(ctrl.Controls)
Next ctrl
End Sub
Second, on your form, use a container like Panel, GroupBox, FlowLayoutPanel, etc for the TextBox controls you need to clear. The key is all of the TextBox controls you need to clear — and none of the ones you want to keep — should be in same common container. Once that is done, you can pass the container to one of the above methods. If this messes with your layout, you can have a small number of containers for sets of controls on different areas of the form and call the function just a few times.
Remember, Panel controls can be styled to leave no visible artifacts on the parent form at all, and used entirely for logical groupings. The second version of the method above will also allow you to create arrays or lists of the controls (or control containers) you care about.
Another way to control this is to inherit a custom control from TextBox. You don't even need to change anything. All that matters is the control is now a different type from a regular textbox, and so the recursive method can target your new control type instead of textbox.
I'm using For in some cases.
First is to know for what do you need Textboxes or any component.
Second is to know if Textboxes (or any other component) will be inside Form (root) or inside others components like panels, groupoxes, tabPages… and if them will be inside of others.
Example1: Form – GroupBox(x) – TabControl(y) – TabPage(z) – TextBox(n)
Example2: Form --- TextBox(x)
Example3: Form – GroupBoox(x) – Panel(y) – TextBox(n)
Etcetera.
You may to create some anidated subs/functions to complete something more elaborated. There are two important things:
Path of the component (see previous examples)
Number of the component. If you follow Example3, maybe could be this:
Form1 – GroupBox2 – Panel1 – TextBox3
Important: These are names of the components, and you need must be enumerated all of them.
The easy way to do what you are asking is:
Public Sub CountTextBoxesAndClear(ByVal FormName As String, Optional ByVal myObject As Object = Nothing)
Dim ArrayTextBoxName() As String
Dim myTextBox As New TextBox
Dim nTBOX As Integer
'Path of component
If myObject = Nothing Then myObject = My.Application.OpenForms.Item(FormName)
'Bucle
For i As Integer = 0 To myObject.Controls.Count - 1
If myObject.Controls(i).GetType Is GetType(TextBox) Then
'Counting
nTBOX += 1
'Redim array
ReDim Preserve ArrayTextBoxName(nTBOX)
'Get Component
ArrayTextBoxName(nTBOX) = "TextBox" & nTBOX
'Get Path
myTextBox = myObject.Controls.Item(ArrayTextBoxName(nTBOX))
'myTextBox = myObject.Controls.Item("TextBox" & nTBOX) '<< the same of above line
Try
'Clear TextBoxes
myTextBox.Clear()
Catch ex As NullReferenceException
'A TextBox is Null, no error message
End Try
End If
Next
End Sub
FormName is the name of Form, with quotes, for example “Form1”.
myObject is the object that contains textboxes, if Textboxes are inside of a Panel named Panel1, you must write Panel1 (without quotes).
Try/Catch: Maybe you need to have Textbox1, TextBox2, Textbox4, Textbox5, AnotherTextBox1, AnotherTextBox2.
And you call your sub:
CountTextBoxesAndClear("Form1")
If TextBoxes are into a Panel named Panel1:
CountTextBoxesAndClear("Form1", Panel1)
You must to have the total of textboxes but only clear (or do any action) only for TextBoxes named TextBox[x].
Try/Catch manage the error because TextBox3 does not exist. However, the correct way is Textbox1, TextBox2, Textbox3, Textbox4, AnotherTextBox1, AnotherTextBox2 and put limits in your sub/function.
For example:
Public Sub CountTextBoxesAndClear(ByVal FormName As String, Optional ByVal myObject As Object = Nothing, Optional byval start as integer = 0, Optional byval finish as integer = 0)
[…tracatra…]
For i As Integer = start To finish
[…tratra…]
Next
End Sub
And this is how to call:
CountTextBoxesAndClear("Form1", Nothing, 1, 4)
And now, you can investigate a little bit about how create subs/functions to know correct paths of components, and get contents and properties of TextBoxes, Labels, Comboboxes, checkboxes…
Additional info:
If you are working in VisualStudio, you know that if you change a name of component, all of code is changed automatically. This is a big problem if you are using start/finish vars as numbers because, you must to change manually all start/finish values in functions when you need to add/remove or move positions, for example:
CountTextBoxesAndClear("Form1", Nothing, 8, 12)
Now you need to add a new TextBox just in the eight position and move one. Your sub looks like this:
CountTextBoxesAndClear("Form1", Nothing, 9, 13)
You can create a simply function that convert the name of the component to integer (this function is only for two digits (0 to 99):
Public Function ObjToInt(ByVal IntObject As Object) As Integer
If IntObject IsNot Nothing Then
Dim ref As Integer = Val(IntObject.Name.Substring(IntObject.Name.Length - 2))
If ref = 0 Then
ref = Val(IntObject.Name.Substring(IntObject.Name.Length - 1))
End If
Return ref
Else
Return 0
End If
End Function
And your sub may be written like this:
CountTextBoxesAndClear("Form1", Nothing, ObjToInt(TextBox9), ObjToInt(TextBox13))
Thanks for your awesome solutions.
I finally figured it out using ParamArray
Public Sub CLEAR_TEXTBOXES(ParamArray arr_textboxes() As TextBox)
For Each textbox As TextBox In arr_textboxes
textbox.Clear()
Next
End Sub
Then i call class using whatever textbox i want,
CLS_CLEAR_TEXTBOX.CLEAR_TEXTBOXES(TextBox1, TextBox2, Textbox7)
It's more shorter method.
Use ParamArray and Linq.
Public Sub CLEAR_TEXT(ParamArray text As TextBox())
text.ToList().ForEach(Sub(s) s.Clear())
End Sub

How to save data into database from dynamically created text boxes

I have dynamically created text boxes on flowlayout panel. The text box can be any number. I am trying to save those value(integer) from text box into database. It takes me a day to achieve this, and i am a newbie by the way. Please guide me how to achieve this. Thank you so much. I try to save into List(Of...) collection but it only returns last value.Here is how i am trying to achieve this. I declare shared list of List type in another class called clsHelper.
Private Sub saveIntoList(flp As FlowLayoutPanel)
clsHelper.list = New List(Of String)
For Each tb in flp.Controls
If TypeOf tb Is TextBox Then
txtNo = DirectCast(tb,TextBox)
If txtNo.Name = "txtNo" Then
clsHelper.list.Add(txtNo.Text)
End If
End If
Next
End Sub
FlowLayoutPanel with six text boxes
The problem is probably this If statement. Only the content of the TextBox whose name is "txtNo" is added to your list. The content of other TextBoxes with different names are not added. Remove the If and the content all TextBoxes will be added to your list.
If txtNo.Name = "txtNo" Then
clsHelper.list.Add(txtNo.Text)
End If
If TextBoxes are not all immediate children of the FlowLayoutPanel, you will need to modify your code as follows, to recursively iterate all the controls hierarchy below the FlowLayoutPanel.
Private Sub saveIntoList(flp As FlowLayoutPanel)
clsHelper.list = New List(Of String)
_saveIntoList(flp)
End Sub
Private Sub _saveIntoList(control As Control)
If TypeOf control Is TextBox Then
clsHelper.list.Add(DirectCast(control, TextBox).Text)
Return
End If
For Each child As Control In control.Controls
_saveIntoList(child)
Next
End Sub

Looping through all Combo boxes on a VB form

I am making a grade application for a school project and I am wondering how I can loop through and check a value in all combo boxes on a certain form, I have 19 units to check; trying to be efficient without making 19 case statements. I have tried an array and me.controls.
Try this :
For Each c As Control In Me.Controls.OfType(Of ComboBox)()
'You cann acces to ComboBox her by c
Next
Note that if you have controls host by containers (TabControl, SPlitPanel, etc.) you may not find all of your controls.
Here is a recursive call that should return all of your controls:
Sub GetControlList(container As Control, ByVal ctlList As List(Of Control))
For Each child As Control In container.Controls
ctlList.Add(child)
If (child.HasChildren) Then
GetControlList(child, ctlList)
End If
Next
End Sub
I have on occasion had problems with the controls on SplitContainer panels so if you use a splitter make sure you are getting all your controls.
Once you have a complete list of controls you can operate on them. This sample is working with DataGridView controls:
Dim ctrls As New List(Of Control)
GetControlList(Me, ctrls)
For Each dgv As DataGridView In ctrls.OfType(Of DataGridView)()
AddHandler dgv.DataError, AddressOf DataGridView_DataError
Debug.Print(dgv.Name)
Next
FYI the generic data error code:
Private Sub DataGridView_DataError(sender As Object, e As DataGridViewDataErrorEventArgs)
Dim dgv As DataGridView = sender
Dim sGridName As String = dgv.Name.Replace("DataGridView", "")
Dim col As DataGridViewColumn = dgv.Columns(e.ColumnIndex)
Dim sColName As String = col.HeaderText
MsgBox(sGridName & vbNewLine & "Column " & sColName & vbNewLine & e.Exception.Message, MsgBoxStyle.Exclamation)
End Sub
You already have the OfType(OF T) method. You use it like this:
ForEach box As ComboBox In MyForm.Controls.OfType(Of ComboBox)()
Next
But this only checks the direct children of your control. If you have container controls like GroupBox, Panels, FlowControlLayoutPanel, etc, you'll miss the controls nested inside them. But we can build a new OfType() method to search these recursively:
Public Module ControlExtensions
<Extension()>
Public Iterator Function OfTypeRecursive(Of T As Control)(ByVal Controls As ControlCollection) As IEnumerable(Of T)
For Each parent As Control In Controls
If parent.HasChildren Then
For Each child As Control In OfTypeRecursive(Of T)(parent.Controls)
Yield child
Next child
End If
Next parent
For Each item As Control In Controls.OfType(Of T)()
Yield item
Next item
End Function
End Module
And you use it the same way:
ForEach box As ComboBox In MyForm.Controls.OfTypeRecursive(Of ComboBox)()
Next
You'll probably want to check containers for controls of the type you're looking for, here's a little function that should do the trick for you.
Private Function GetControls(Of T)(container As Control, searchChildren As Boolean) As T()
Dim Controls As New List(Of T)
For Each Child As Control In container.Controls
If TypeOf Child Is T Then
DirectCast(Controls, IList).Add(Child)
End If
If searchChildren AndAlso Child.HasChildren Then
Controls.AddRange(GetControls(Of T)(Child, True))
End If
Next
Return Controls.ToArray()
End Function
Here's the usage, if search children is True then all child containers will be searched for the control you're looking for. Also, for the top most container we'll just pass in Me assuming you're calling the code from within your Form, otherwise you could pass the Form instance or a specific Panel, GroupBox, etc.
Dim ComboBoxes As ComboBox() = GetControls(Of ComboBox)(Me, True)

Issue when binding combobox and listbox to List (VB.NET)

I'm trying to bind a ComboBox and a ListBox to a List(Of String) in Vb.Net (VS2013), this is for a WinForms application, the thing is that after setting the DataSource property on both the ComboBox and the ListBox, selecting one item on either one of them affects the other control, for example, after the controls have been populated with the information if I select an item from the ListBox, then the same item gets selected in the ComboBox, and this goes the same way for the ComboBox, if I select an item from it, then that item gets also selected in the ListBox, so my question is... how can I bind the combobox and listbox to the same List(Of String) without affecting the behavior on the controls, the purpose is to keep all the controls within that form syncronized based on the contents of the List, I declared the List in a module like this:
Public listaAreas As New List(Of String)
then the controls are populated like this on form load:
cmbArea.DataSource = listaAreas
lstAreas.DataSource = listaAreas
And I run this method whenever I need to update the information:
Private Sub RefreshLists()
lstAreas.DataSource = Nothing
lstAreas.DataSource = listaAreas
cmbArea.DataSource = Nothing
cmbArea.DataSource = listaAreas
End Sub
Please let me know if I'm missing some information, this is my first post but I think it is clear enough so you get the idea of what I'm trying to accomplish here ... =)
Thanks in advance!
Try setting up separate BindingSources and try using a BindingList(Of String) instead of just a List, which won't report item changes:
Private listaAreas As New BindingList(Of String)
Private cbSource As New BindingSource(listaAreas, String.Empty)
Private lbSource As New BindingSource(listaAreas, String.Empty)
Public Sub New()
InitializeComponent()
cmbArea.DataSource = cbSource
lstAreas.DataSource = lbSource
End Sub
The same currency position is being used in your code, but by defining two separate binding sources, each one will have its own position property.