How to refer to controls using variable in a loop? - vb.net

I have labels named: label1, label2, ...label16. I want to assign a random number to all of them.
Something like this could work but I don't know the syntax:
for i = 1 to 16
label(i).text = Math.Ceiling(Rnd() * 99)
next
Your suggestions would be appreciated.

You can use this using Controls.Find:
For i As Integer = 1 To 16
Dim arrCtrl() As Control = Me.Controls.Find("label" & i, True)
If arrCtrl.Length = 1 AndAlso TypeOf arrCtrl(0) Is Label Then
DirectCast(arrCtrl(0), Label).Text = Math.Ceiling(Rnd() * 99)
End If
Next

Getting a random Integer
Use the Random class instead of the Rnd function to get a random Integer within a specified range in the Random.Next(Int32, Int32) method. Declare a class variable of Random type:
Private ReadOnly rand As New Random
Finding a range of controls
This code snippet iterates over the Controls collection of the container, returns - if any - the Label controls where their names are equals to a range of names starts from label1 to label16, and finally, assign a random Integer to their Text properties:
Private Sub TheCaller()
For Each lbl In Controls.OfType(Of Label).
Where(Function(x) Enumerable.Range(1, 16).
Any(Function(y) x.Name.ToLower.Equals($"label{y}")))
lbl.Text = rand.Next(1, 100).ToString
Next
End Sub
Just in case, if the Label controls are hosted by different containers, then you need a recursive function to get them:
Private Function GetAllControls(Of T)(container As Control) As IEnumerable(Of T)
Dim controls = container.Controls.Cast(Of Control)
Return controls.SelectMany(Function(x) GetAllControls(Of T)(x)).
Concat(controls.OfType(Of T))
End Function
And call it as follows:
Private Sub TheCaller()
For Each lbl In GetAllControls(Of Label)(Me).
Where(Function(x) Enumerable.Range(1, 16).
Any(Function(y) x.Name.ToLower.Equals($"label{y}")))
lbl.Text = rand.Next(1, 100).ToString
Next
End Sub

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim labels As New List(Of Label) From {Label1, Label2, Label3, Label4}
For Each l As Label In labels
l.Text = rand.Next(99).ToString
Next
End Sub
To use your approach
Declare a variable for the Random class outside your method (a Form level variable).
Create a List of labels.
Loop through all the labels in your list and set the .Text property with the .Next method of the Random class.

Related

Listbox Sorting Issue

I am having trouble sorting a listbox the way I am expecting it to work even using the sorted = true property. Assuming i have files named as such, when i sort by "Name" in a Windows Folder view (outside of vb) it sorts like so:
1180741
1179715
1162371
1141511
1131750
1117362
1104199
1082698
1062921
1043875
991514
970621
963154
952954
948067
917669
904315
899902
892398
882024
This is how i need it to be sorted in my list. However, with the sorted = true property, vb.net decides to sort like this:
1043875
1062921
1082698
1104199
1117362
1131750
1141511
1162371
1179715
1180741
882024
892398
899902
904315
917669
948067
952954
963154
970621
991514
I cannot understand why windows sorts the list correctly but VB does not. Any help would be most appreciated.
*This example assumes that each object in the listbox is a string representation of an integer, if in the case that it is not, the item will be skipped, you can modify this to handle such a case in another way if you want....
Example:
Option Strict On
Option Explicit On
Option Infer Off
Public Class Form1
Dim r As New Random(Now.Millisecond)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'We simulated a populated listbox
For I As Integer = 0 To 1000
Dim n As Integer = r.Next(0, Integer.MaxValue)
ListBox1.Items.Add(n.ToString)
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SortListBoxNumbers(ListBox1)
End Sub
Sub SortListBoxNumbers(listbox As ListBox)
Dim numbers As New List(Of Integer)
For Each item As Object In listbox.Items
Dim n As Integer = 0
If item.GetType = GetType(String) Then
If Integer.TryParse(DirectCast(item, String), n) Then
numbers.Add(n)
End If
End If
Next
listbox.Items.Clear()
numbers = SortNumbers(numbers)
For Each n As Integer In numbers
listbox.Items.Add(n.ToString)
Next
End Sub
Function SortNumbers(numbers As List(Of Integer)) As List(Of Integer)
Dim result As New List(Of Integer)
Dim count As Integer = numbers.Count
Do
Dim highN As Integer = Integer.MinValue
For Each n As Integer In numbers
If n > highN Then highN = n
Next
numbers.Remove(highN)
result.Insert(result.Count, highN)
Loop Until result.Count = count
result.Reverse()
Return result
End Function
End Class

the function does not seem to change value when called upon 3 times

I am writing code in visual basic. the goal of the program is to get a function to randomly pick a string from a array when called upon. the problem is it is not choosing a different value when the button calls it to do so. I have no idea why it is doing this.
Also anyone know how to get multlines to print in a label or text? here is my code.
Public Class lbl
Public Function Noun() As String
Dim rand As New Random
Dim index As Integer
Dim nouns() As String = {"boy", "girl", "dog", "town", "car"}
Dim sentence As String ' used to build a sentence
index = rand.Next(5)
sentence = nouns(index)
Return sentence
End Function
Public Function outs(ByVal n1 As String) As String
Dim result = n1
Return result
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
lblRandom.Text = outs(Noun()) & outs(Noun())
lblRandom1.Text = outs(Noun())
End Sub
Public Shared Function RandomNumber(ByVal lowerBound As Integer, ByVal upperBound As Integer) As Integer
Return CInt(Math.Floor((upperBound - lowerBound + 1) * Rnd())) + lowerBound
End Function
Public Function Noun() As String
Dim rand As New Random
Dim index As Integer = -1
Dim nouns() As String = {"boy", "girl", "dog", "town", "car"}
Dim sentence As String ' used to build a sentence
index = RandomNumber(0, 4)
sentence = nouns(index)
Return sentence
End Function
Public Function outs(ByVal n1 As String) As String
Dim result = n1
Return result
End Function
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
lblRandom.Text = outs(Noun()) & outs(Noun())
lblRandom1.Text = outs(Noun())
End Sub
The problem is that you re-create a new random generator each time the Noun() function is called. The random number generator should be a global variable.
The problem is that the .NET random number generator generates random numbers based on a seed. The seed is an integer based on the current system date and time. If you call the random number generator a number of times in quick succession, the same seed is likely to be used and the same "random" number will be generated. The solution is to declare the Random object outside of the procedure in which you wish to generate the random number and to pass a seed to its constructor which is built from the current (random) date and time.
To answer your other questions. Label controls will automatically wrap text if the text contains carriage returns. I have added a carriage return below for lblRandom.
To wrap text in a text box, set its Multiline property to True.
The code that #Aly El-Haddad posted will also work but it contains some VB6 legacy code. I have tried to keep it as .NET as possible. I have also eliminated the superfluous "outs" procedure and made it flexible so that you can add or delete elements in the "nouns" array. Note that with the line "index = rand.Next(5)", you are including element 5 of the array which doesn't exist. The upper bound of the zero-based array is 4 so an "Index was outside the bounds of the array" error will be thrown when your random number generator returns 5.
Private myRandom As New Random(CType(DateTime.Now.Ticks Mod Int32.MaxValue, Integer))
Private Function Noun() As String
Dim index As Integer
Dim nouns() As String = {"boy", "girl", "dog", "town", "car"}
index = myRandom.Next(nouns.GetLowerBound(0), nouns.GetUpperBound(0))
Return nouns(index)
End Function
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
lblRandom.Text = Noun() & Constants.vbNewLine & Noun()
lblRandom1.Text = Noun()
End Sub

Checking to see if Cursor is in rectangle bounds

I have a program that randomly sets the cursor that an x,y coordinate and then clicks. I have this function/sub that creates a rectangle based on certain parameters.
Private Sub drawTitleBarRectangle()
Dim titleBarRectangle As Rectangle = RectangleToScreen(Me.ClientRectangle)
Dim titleBarRectangleHeight As Integer = titleBarRectangle.Top - Me.Top
Dim titleBarRectangleWidth As Integer = Screen.PrimaryScreen.Bounds.Width
Dim titleBarRectangleTop As Integer = Screen.PrimaryScreen.Bounds.Top
Dim titleBarBounds As New Drawing.Rectangle(0, 0, titleBarRectangleWidth, titleBarRectangleHeight)
End Sub
I want to check if when the cursor is at it's x,y position if it is within the bounds of the rectangle created from that function or not. Right now I have this:
drawTitleBarRectangle()
SetCursorPos(x, y)
If titleBarRectangle.Contains(x, y) Then
leftClick(800, 800)
End If
The Private titleBarRectangle is from a global variable that I declare as Private titleBarRectangle As New Drawing.Rectangle I'm not too sure why it's there to be honest...
Any help would be appreciated.
All of the variables in the initial method you've listed are local variables. This means they are simply discarded when that method exits. You need to update the class level variable you've declared by making an assignment instead of a declaration. With that in mind, it should look more like:
Public Class Form1
Private titleBarRectangle As Rectangle
Private Sub drawTitleBarRectangle()
Dim rc As Rectangle = Me.RectangleToScreen(Me.ClientRectangle)
Dim titleBarRectangleHeight As Integer = rc.Top - Me.Top
titleBarRectangle = New Rectangle(Me.Location.X, Me.Location.Y, Me.Width, titleBarRectangleHeight)
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
drawTitleBarRectangle()
Debug.Print(titleBarRectangle.ToString)
ControlPaint.DrawReversibleFrame(titleBarRectangle, Color.Black, FrameStyle.Dashed)
Dim x As Integer = titleBarRectangle.Location.X + titleBarRectangle.Width / 2
Dim y As Integer = titleBarRectangle.Location.Y + titleBarRectangle.Height / 2
Cursor.Position = New Point(x, y)
If titleBarRectangle.Contains(Cursor.Position) Then
Debug.Print("It's in there!")
End If
End Sub
End Class
Notice how the last line in the method will use the class level variable instead of a local since we don't have Dim in front of it.

Using string as object name

I'm trying to use string as object name. Example I have an object and has a name = Label1. Can I do this?
Dim i As String = "Label1"
someVariable = i.Text
I'm using string as object name, is it possible?
You could iterate over all of the controls as #Christian Sauer said but you might run into problems if any controls are containers of controls. You'd need to do a recursive search to solve that. However, the ControlCollection actually has a Find() method that you can use. It returns an array of controls that match the name and optionally performs a recursive search.
''//Our final control
Dim someVariable As Control = Nothing
''//Search recursively for our control
Dim SearchedControls = Me.Controls.Find(key:="Label1", searchAllChildren:=True)
''//If we found one and only one control
If SearchedControls.Count = 1 Then
''//Set it to our variable
someVariable = SearchedControls(0)
Else
''//Whatever your logic dictates here
End If
This is not possible - but what you can do:
Dim i As String = "Label1"
Dim Obj as Label
for each elem in me.controls
if elem.Name = i then
Obj = elem
exit for
end if
next
someVariable = obj.Text
I am iterating over all WinForms control to find the label with the Name "Label1" - when found, i assign the label to a Variable.
This works, but can be quite dangerous, especially if you add controls
I know it's been answered, but this is from my library, and I use it all the time. It will iterate over all controls, and containers' controls recursively as #ChrisHaas suggested.
Public Function GetControlByName(ByRef parent As Control, ByVal name As String) As Control
For Each c As Control In parent.ChildControls
If c.Name = name Then
Return c
End If
Next
Return Nothing
End Function
<Extension()> _
Public Function ChildControls(ByVal parent As Control) As ArrayList
Return ChildControls(Of Control)(parent)
End Function
<Extension()> _
Public Function ChildControls(Of T)(ByVal parent As Control) As ArrayList
Dim result As New ArrayList()
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is T Then result.Add(ctrl)
result.AddRange(ChildControls(Of T)(ctrl))
Next
Return result
End Function
(It's been asked and answered before)
Loop Through Controls on Web User Control
I'm sure it's answered but some points to be clear it's control array and result so as to be sure it's corrected.
Private Sub btn1_click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn1.Click
Dim Ds As New DataSet
Dim str As String = ""
str = " SELECT TOP (10) t_Suppliers.STNo from t_Suppliers "
Ds = SqlHelper.ExecuteDataset(ConnectionString, CommandType.Text, str)
For i As Integer = 0 To Ds.Tables(0).Rows.Count - 1
Dim str1 As String = "lblInv" & i + 1
Dim OBj As New Label
Try
Dim SearchedControls() As Control = Me.Controls.Find(key:=str1, searchAllChildren:=True)
If SearchedControls.Length > 0 Then
SearchedControls(0).Text = Ds.Tables(0).Rows(i).Item("STNo").ToString
End If
Catch
End Try
Next
End Sub
I found the following solution on another site.
It works.
--Quote -
Dim TextBox As TextBox
Dim I As Integer = 2
Dim name As String = "TextBox" & I.ToString
TextBox = Me.Controls.Item(name)
TextBox.Text = "Something special"

Resizing an array at runtime in VB.NET

In my Windows Forms application at runtime I will be resizing an array each time I add an element. So first I must resize to the size + 1, and then add a member to this index. How do I do this?
You could use the ReDim statement, but this really isn't your best option. If your array will be changing sizes often, especially as it sounds like you're just appending, you should probably use a generic List(Of T) or similar collection type.
You can use it just like you use an array, with the addition that adding an item to the end is as easy as MyList.Add(item)
To use a generic list, add Imports System.Collections.Generics to the top of the file. Then, you would declare a new integer list like this:
Dim MyList As New List(Of Integer)()
or a string list like this:
Dim MyList As New List(Of String)()
You should get the idea.
The suggested ReDim's need the Preserve keyword for this scenario.
ReDim Preserve MyArray(n)
Using a generic list is (as suggested) the best idea. If you however want to change the size of an Array, you can use Array.Resize(ByRef arr, newSize).
ReDim is not a good (pretty bad) idea (VB specific legacy, extremely slow).
I would prefer some type of collection class, but if you WANT to use an array do it like this:
Dim arr() As Integer
Dim cnt As Integer = 0
Dim ix As Integer
For ix = 1 To 1000
cnt = cnt + 1
ReDim arr(cnt)
arr(cnt - 1) = ix
Next
You can also make your own collection class. A good programming exercise for new programmers.
Public Class MyList
Private Items() As String
Private No As Integer = 0
Public Sub Add(ByVal NewItem As String)
''Create a temporary new string array
Dim CopyString(No) As String
''Copy values from Global Variable Items() to new CopyString array
For i As Integer = 0 To No - 1
CopyString(i) = Items(i)
Next
''Add new value - NewItem - to CopyString
CopyString(No) = NewItem
''Increment No to No + 1
No += 1
''Copy CopyString to Items
Items = CopyString
'Discard CopyString
CopyString = Nothing
End Sub
Public Sub Show(ByVal index As Integer)
MsgBox(Items(index))
End Sub
End Class
''Now create a form with a TextBox name - txt, Button1 and Button2
Public Class Form1
''Declare txts as a new MyList Class
Private txts As New MyList
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
''Add text to txts which is a MyList Class
txts.Add(txt.Text)
txt.Text = ""
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
''Display value at a specific index
txts.Show(Convert.ToInt16(txt.Text))
txt.Text = ""
End Sub
End Class
Use the ReDim command to specify the new size.
ReDim MyArray(MyArray.Length + 1)
As Joel says, use a list.
Dim MyList As New List(Of String)
Don't forget to change Of String to be Of whichever datatype you're using.
This work for me
Dim Table1 As New DataTable
' Define columns
Table1.Columns.Add("Column1", GetType(System.String))
Table1.Columns.Add("Column2", GetType(System.Int32))
Table1.Columns.Add("Column3", GetType(System.Int32))
' Add a row of data
Table1.Rows.Add("Item1", 44, 99)
Table1.Rows.Add("Item2", 42, 3)
Table1.Rows.Add("Item3", 42, 3)
Table1.Rows.Add("Item4", 42, 3)
Dim arr(-1) As String
For Each dr As DataRow In Table1.Rows
ReDim Preserve arr(arr.Length)
arr(arr.Length - 1) = dr("Column1")
Next