Fitting runtime buttons inside a form - vb.net

I have a number of buttons between 5-20 and it's variable each time the form loads based on the user settings. I am trying to fit all these buttons on my form no matter what the size of the form is. The buttons are generated during runtime. I would like the first button to be 20 points from the top bar (at any size) and the rest of the buttons simply to fit in the form. This is what I have now but I have to maximize the form to view them all and also while I'm expanding the form the space between the buttons decreases and they overlap with each other whereas they should keep a relative distance. Any ideas?
Dim iButtonWidth, iButtonHeight, iVerticalSpace As Integer
If UserButtons.Count > 0 Then
iButtonHeight = Me.Size.Height - (Me.Size.Height * 0.85)
iButtonWidth = Me.Size.Width - (Me.Size.Width * 0.5)
iVerticalSpace = iButtonHeight / 3
For Each btn In UserButtons
btn.Size = New System.Drawing.Size(iButtonWidth, iButtonHeight)
btn.Location = New Point(20, 20 + btn.TabIndex * iVerticalSpace * 3)
Next
End If

Here's a quick example using the TableLayoutPanel to play with:
Public Class Form1
Private UserButtons As New List(Of Button)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Static R As New Random
Dim NumButtons As Integer = R.Next(5, 21) ' "a number of buttons between 5-20 and it's variable each time"
UserButtons.Clear()
For i As Integer = 1 To NumButtons
Dim btn As New Button()
btn.Text = i.ToString("00")
btn.Dock = DockStyle.Fill ' Optional: See how you like it with this on vs. off
UserButtons.Add(btn)
Next
DisplayButtons()
End Sub
Private Sub DisplayButtons()
TableLayoutPanel1.SuspendLayout()
TableLayoutPanel1.Controls.Clear()
TableLayoutPanel1.ColumnStyles.Clear()
TableLayoutPanel1.ColumnCount = 5 ' Fixed Number of Columns
For i As Integer = 1 To TableLayoutPanel1.ColumnCount
TableLayoutPanel1.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 911)) ' the size doesn't matter here, as long as they are all the same
Next
' Variable Number of Rows:
Dim RowsRequired As Integer = ((UserButtons.Count - 1) \ TableLayoutPanel1.ColumnCount) + 1 ' Integer Division
TableLayoutPanel1.RowStyles.Clear()
TableLayoutPanel1.RowCount = RowsRequired
For i As Integer = 1 To TableLayoutPanel1.RowCount
TableLayoutPanel1.RowStyles.Add(New RowStyle(SizeType.Percent, 911)) ' the size doesn't matter here, as long as they are all the same
Next
TableLayoutPanel1.Controls.AddRange(UserButtons.ToArray)
TableLayoutPanel1.ResumeLayout()
End Sub
End Class

First of all what kind of container are the buttons in? You should be able to set the container's AutoScroll property to true - then when controls within it spill out of the visible bounds you will get a scroll bar.
Then also what you could do is draw each button in more of a table with a certain number next to each other before dropping down to the next line (instead of just 1 button on each line). If that is an option that works for you then you could get more buttons within the visible space.
I happen to have an example to do the same thing with picture boxes (and text boxes under each picture box). Hope this helps:
Dim point As New Point(0, 0)
'create 11 picture and text boxes-you can make this number the number your user selects.
Dim box(11) As PictureBox
Dim text(11) As TextBox
Dim i As UInt16
For i = 0 To 11 'or whatever your number is
box(i) = New PictureBox
box(i).Width = 250 'your button width
box(i).Height = 170 'your button height
box(i).BorderStyle = BorderStyle.FixedSingle
box(i).Location = point
layoutsPanel.Controls.Add(box(i)) 'my container is a panel
text(i) = New TextBox
text(i).Height = 50
text(i).Width = 250
point.Y += box(i).Height
text(i).Location = (point)
layoutsPanel.Controls.Add(text(i))
point.Y -= box(i).Height 'reset Y for next picture box
'Put 4 picture boxes in a row, then move down to next row
If i Mod 4 = 3 Then
point.X = 0
point.Y += box(i).Height + text(i).Height + 4
Else
point.X += box(i).Width + 4
End If
Next

Related

How to dynamicallty create multiple controls at runtime

I am trying to add multiple labels to a userform at runtime
It's for the player names of a board game; and until the game starts the number of players are not known. I have managed to figure out for myself how to use the dynamic array function to create the list of players. I used a For.....Next loop to add the player names. I thought I could do that to add the labels to the form, but it only adds one. Depending on where the new control type is declared, it either adds the first player only, or the last player
This code produces one label only within the groupbox, the last player
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Players_Num As Integer = InputBox("Enter the number of players")
Dim Players(Players_Num) As String
Dim newText As New Label
For i = 0 To Players_Num - 1
Players(i) = InputBox("Enter player name")
Next
'This piece of code was jsut for me to test that I was successfully using a For...Loop
'to add the players names, and will be deleted later on
For x = 0 To Players_Num - 1
MessageBox.Show(Players(x))
Next
For z = 0 To Players_Num - 1
newText.Name = "txt" & Players(z)
newText.Text = Players(z)
newText.Size = New Size(170, 20)
newText.Location = New Point(12 + 5, 12 + 5)
GroupBox1.Controls.Add(newText)
Next
End Sub
End Class
This one places only the first player
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Players_Num As Integer = InputBox("Enter the number of players")
Dim Players(Players_Num) As String
For i = 0 To Players_Num - 1
Players(i) = InputBox("Enter player name")
Next
'This piece of code was jsut for me to test that I was successfully using a For...Loop
'to add the players names, and will be deleted later on
For x = 0 To Players_Num - 1
MessageBox.Show(Players(x))
Next
For z = 0 To Players_Num - 1
Dim newText As New Label
newText.Name = "txt" & Players(z)
newText.Text = Players(z)
newText.Size = New Size(170, 20)
newText.Location = New Point(12 + 5, 12 + 5)
GroupBox1.Controls.Add(newText)
Next
End Sub
End Class
I've tried this in vs 2015 and 2019 Community
Where is it going wrong?
From the looks of the code, you are correctly creating the controls but their location is the same for all of them, essentially, they are being place one of top of the other, the first is hidden with the second, which is hidden with the third.
The line
newText.Location = New Point(12 + 5, 12 + 5)
needs to be modified to place the labels at different locations.
Perhaps, something like:
newText.Location = New Point(12 + 5, 12 + (z * 25))
This will vertically align the labels with a gap of 25 between them
You are placing them all in the same location
newText.Location = New Point(12 + 5, 12 + 5)
Use your 'z' index to place them at different locations in order to be able to see them
For me it is easier to contain controls in a TableLayoutPanel then add the TLP to what ever control collection, such as a GroupBox This way you can couple a Label with TextBox, for example. Here's an example how you can create controls from a DataTable. In your case you would only need 1 ColumnStyle for labels, I just thought I would show you a good practice for future shortcuts. (I rarely use the designer to place controls)
'Start test data
Dim DtTable As New DataTable
With DtTable
Dim NewDtRow As DataRow = .NewRow
For i As Integer = 0 To 25
Dim DtCol As New DataColumn With {.ColumnName = "Col" & i, .DataType = GetType(String)}
.Columns.Add(DtCol)
NewDtRow(DtCol.ColumnName) = "Test" & i
Next
.Rows.Add(NewDtRow)
End With
'End test data
Dim TLP1 As New TableLayoutPanel With {.Name = "TlpFields"}
With TLP1
.BorderStyle = BorderStyle.Fixed3D
.CellBorderStyle = TableLayoutPanelCellBorderStyle.Inset
.AutoScroll = True
.AutoSize = True
.RowStyles.Clear()
.ColumnStyles.Clear()
.Dock = DockStyle.Fill
.ColumnCount = 2
.ColumnStyles.Add(New ColumnStyle With {.SizeType = SizeType.AutoSize})
End With
For Each DtCol As DataColumn In DtTable.Columns
With TLP1
.RowCount += 1
.RowStyles.Add(New RowStyle With {.SizeType = SizeType.AutoSize})
'create labels
.Controls.Add(New Label With {
.Text = DtCol.ColumnName,
.Anchor = AnchorStyles.Right}, 0, .RowCount)
'create textboxs
Dim TxtBox As New TextBox With {
.Name = "TextBox" & DtCol.ColumnName,
.Size = New Size(170, 20),
.Anchor = AnchorStyles.Left}
'add binding
TxtBox.DataBindings.Add("Text", DtTable, DtCol.ColumnName)
.Controls.Add(TxtBox, 1, .RowCount)
End With
Next
Controls.Add(TLP1)

Adding labels and other controls to a groupbox dynamically in vb.net not working?

So I've been having problems adding labels from a record structure into a groupbox that stack below each other vertically. I could use a Flow Layout Panel but I have multiple labels I need to add from data in a record structure which I want to be able to scroll later with a vertical scrollbar as the autoscroll will only work with one panel only, and not all of them.
For some reason the program puts only one label in, and the others don't seem to be visible even though they have been created (I used a messagebox to check). Could someone please help me to put them in, as I am fairly new to programming and need help.
For context, the program loads 'materials' from an xml file and stores it in a record structure, then this bit of the program creates labels and radio buttons dynamically and puts it in a groupbox, so that it can be arranged manually on a pretty grid that will all become scrollable by using a scrollbar later.. Because the data I am working with is all related, and can have strings of multiple sizes (names and suppliers of materials for example) I didn't want to attempt to add whitespace to make the grid work but with a single label (not that the others would show beneath the first for some reason)
CODE:
'sets up Labels for editing materials
prgFunctions.loadMat()
Dim counter As Integer
Dim newMatIDLabel(numMatFile) As Label
Dim newMatRdb(numMatFile) As RadioButton
Dim lastPos As Integer
'testing to see if materials load GET RID OF LATER
' For counter = 1 To numMatFile
'ListBox1.Items.Add(materials(counter).matName)
' Next
'create the labels with information
For counter = 1 To numMatFile
'ID
newMatIDLabel(counter) = New Label
newMatIDLabel(counter).Name = "lblMatIDNum" & counter
newMatIDLabel(counter).Text = materials(counter).matID
newMatIDLabel(counter).Parent = gbxMaterials
' MsgBox(newMatIDLabel(counter).Name & " " & newMatIDLabel(counter).Text)
Next
'create the checkboxes NOW RADIO BUTTONS
For counter = 1 To numMatFile
newMatRdb(counter) = New RadioButton
newMatRdb(counter).Name = "chkMatSelectNum" & counter
newMatRdb(counter).Text = ""
newMatRdb(counter).Parent = gbxMaterials
Next
'matID locations
lastPos = 10
For counter = 1 To numMatFile
'SOMEHOW MOVE IT INTO THE GROUPBOX INSTEAD, as issues arise everywhere
newMatIDLabel(counter).Location = New Point(7, lastPos + 10)
lastPos = lastPos + 10
Next
Why not just:
lastPos = 10
For counter = 1 To numMatFile
Dim label As New Label
label.Name = "chkMatSelectNum" & counter
label.Text = materials(counter).matID
label.Location = New Point(7, lastPos)
label.Visible = True
gbxMaterials.Controls.Add(label)
lastPos += label.Height
Next
Based on F0r3v3r-A-N00b's answer:
The label has a height of 23, so it seems it is hiding the others.
This works for me:
Dim lastPos As Integer = 20
For counter As Integer = 1 To numMatFile
Dim label As New Label
label.Name = "chkMatSelectNum" & counter
label.Text = materials(counter).matId
label.AutoSize = True
label.Visible = True
label.Location = New Point(7, lastPos)
gbxMaterials.Controls.Add(label)
lastPos += 17
Next

Problems with generating labels

The Problem: I'm programmatically generating labels but am having trouble referencing them in code because they don't exist at runtime.
The Context: For a game, I've generated a 10x10 grid of labels with the following:
Public lbl As Label()
Dim tilefont As New Font("Sans Serif", 8, FontStyle.Regular)
Private Sub Lucror_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim i As Integer = 0
Dim a As Integer = 0
Dim height As Integer
Dim width As Integer
height = 30
width = 30
ReDim lbl(99)
For i = 0 To 99
lbl(i) = New Label
lbl(i).Name = i
lbl(i).Size = New System.Drawing.Size(30, 30)
lbl(i).Location = New System.Drawing.Point((width), height)
lbl(i).Text = i
lbl(i).Font = tilefont
Me.Controls.Add(lbl(i))
width = width + 30
a = a + 1 'starting new line if required
If (a = 10) Then
height = height + 30
width = 30
a = 0
End If
Next
End Subenter code here
This worked fine but the labels function as tiles in the game and game tiles need to store 2-3 integers each as well as be able to be referenced through event handlers. I figured a possible way to store integers would be to generate 100 arrays, each named after a label and each holding the 2-3 integers, but that seems very redundant.
What I need:
On click and on hover event handlers for every label
An array (or dictionary?) to store 2-3 integers for every label
Labels have to reference each others names ie. do something to label with name (your name + 1).
The Question: Is there a simple way to achieve these three things with the current way I generate labels (and if so, how?) and if not, how else can I generate the 100 labels to make achieving these things possible?
Any help is much appreciated.
Your labels do exist at runtime, but not at compile time. Attaching events is a little different at runtime, you must use AddHandler.
Below is some sample code that should illustrate everything you're asking for. I've introduced inheritance as a way of saving data that is pertinent to each tile. The GameTile type behaves exactly as a label, and we've added some functionality for storing integers and naming the control.
Public Class Form1
Dim tilefont As New Font("Sans Serif", 8, FontStyle.Regular)
Public Property GameTiles As List(Of GameTile)
Private Sub Lucror_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim a As Integer = 0
Dim xPosition As Integer = 30
Dim yPosition As Integer = 30
GameTiles = New List(Of GameTile)
For i = 0 To 99
Dim gt As New GameTile(i.ToString)
gt.Size = New System.Drawing.Size(30, 30)
gt.Location = New System.Drawing.Point((yPosition), xPosition)
gt.Font = tilefont
gt.Integer1 = i + 1000
gt.Integer2 = i + 2000
gt.Integer3 = i + 3000
Me.Controls.Add(gt)
AddHandler gt.Click, AddressOf TileClickHandler
GameTiles.Add(gt)
yPosition = yPosition + 30
a = a + 1 'starting new line if required
If (a = 10) Then
xPosition = xPosition + 30
yPosition = 30
a = 0
End If
Next
End Sub
Private Sub TileClickHandler(sender As Object, e As EventArgs)
Dim gt = CType(sender, GameTile)
MsgBox("This tile was clicked: " & gt.Text &
Environment.NewLine & gt.Integer1 &
Environment.NewLine & gt.Integer2 &
Environment.NewLine & gt.Integer3)
End Sub
End Class
Public Class GameTile
Inherits Label
'this class should be in a separate file, but it's all together for the sake of clarity
Public Property Integer1 As Integer
Public Property Integer2 As Integer
Public Property Integer3 As Integer
Public Sub New(NameText As String)
MyBase.New()
Name = NameText
Text = NameText
End Sub
End Class

Arranging Buttons inside a Panel vb.net

I am trying to put 25 Buttons inside a Panel in VB.Net 2015 Classic Form Like the Picture but it is not working . could you please help... bellow my code
Dim i As Integer
For i = 1 To 25
newButton = New Windows.Forms.Button
newButton.AutoSize = True
newButton.Name = "btnButton" & i
newButton.Text = "Button " & i
newButton.Top = i * 5
newButton.Left = i * 25
newButton.Size = New Size(95, 70)
AddHandler newButton.Click, AddressOf ButtonClicked
Panel1.Controls.Add(newButton)
Next
Your code is creating the buttons, and the problem is that they are not arranged properly so what you need to do is arrange them in rows and columns. here i help you to do this;
Here this snippet will tell you how to arrange them in 5 columns and n rows:
Dim x As Integer = 5 ' x co-ordinate of the point
Dim y As Integer = 5 ' y co-ordinate of the point
For i = 1 To 25
If i Mod 5 = 0 Then ' For starting next row after column
y += 100 ' 100 is not mandatory change as per size of button
x = 0
Else
x += 100 ' 100 is not mandatory change as per size of button
End If
Dim p As Point = New Point(x, y)
Dim newButton = New Windows.Forms.Button
newButton.Location = p
//do the rest of formatting here
Panel1.Controls.Add(newButton)
Next

Display output on the form in VB 2010

I'm designing a windows form. I have output to be displayed on the form it self.
Tried using print, but it is not working.
How do I do that?
I'M NOT PRINTING THE FORM.
ADDED:
I need to display 3 numbers with text string next to each number.
I want to do this in a way that it shows in the form or label in the form without overwriting the previous results.
example:
3 (wrong) 1 (right) 8 (wrong)
2 (wrong) 1 (right) 5 (right)
9 (right) 1 (right) 5 (right)
ADDED:
Thanks for the help everyone. one more question and i think i'm good.
I was thinking of doing something like this inside a loop, problem is I can't add a string and an int together to make a new var:
Xnum1 = Xnum1 + 50
Xnum2 = Xnum1 + ".0F"
Ynum1 = Ynum1 + 50
Ynum2 = Ynum1 + ".0F"
In VB6 you could use the Print statement to draw to the surface of the form. In VB.NET, however, you should be using the Form.CreateGraphics method to create a new Graphics object that can be used to draw to the form's surface. For instance:
Private Sub PrintText(text As String, x As Single, y As Single)
Dim g As Graphics = Me.CreateGraphics()
g.DrawString(text, New Font("Arial", 16), New SolidBrush(Color.Black), New PointF(x, y))
End Sub
That would be the closest equivalent to using the VB6 Print statement like that.
However, I would strongly recommend using a control to display the data. It looks like for the data you need to display, a simple multi-line text box or label would be sufficient. For instance:
Private Sub AppendResult(index As Integer, right As Boolean)
If right Then
TextBox1.Text = TextBox1.Text & " " & index.ToString() & " (right)"
Else
TextBox1.Text = TextBox1.Text & " " & index.ToString() & " (wrong)"
End If
End Sub
If you want to get more fancy, you could look into using a data grid, a list box, a list view, or even a table layout control instead.
I believe that the most efficient way is to use a tableLayoutPanel with 6 columns. Add in each cell a label showing in the first cell the number, in the second the indicator for that number (right/wrong). Do the same for second and third number.(second number = third and fourth cell, third number =fifth and sixth cell)
For the next set of numbers you can add a new row with with labels in each cell.
I'll add some code to make my answer more professional.
First you add the tableLayoutPanel in your form. You size it as you like (make its width, long enough to handle the data)
You delete the lastRow and then you add columns (you want to have 6 columns). You edit the size of the columns to be Percentage =16.67%
Public Class Form1
Private rowIndex
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
For i = 0 To 4 Step 2
Dim val As Integer = 3
AddLabels(val, i, 0)
Next
For i = 1 To 5 Step 2
Dim val As String = "right"
AddLabels(val, i, 0)
Next
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
rowIndex = rowIndex + 1
Me.TableLayoutPanel1.RowStyles.Add(New RowStyle(SizeType.Absolute, 30))
Me.TableLayoutPanel1.Height = Me.TableLayoutPanel1.Height + 30
For i = 0 To 4 Step 2
Dim val As Integer = 3 'here you have to put your number
AddLabels(val, i, rowIndex)
Next
For i = 1 To 5 Step 2
Dim val As String = "right" 'here you have to put your indicator
AddLabels(val, i, rowIndex)
Next
End Sub
Private Sub AddLabels(ByVal lblValue As String, ByVal column As Integer, ByVal row As Integer)
Dim lblHeader As New Label
lblHeader.AutoSize = True
lblHeader.Margin = New Padding(0)
lblHeader.BackColor = Color.Transparent
lblHeader.TextAlign = ContentAlignment.MiddleLeft
lblHeader.Dock = DockStyle.None
lblHeader.Text = lblValue
'Put the lblHeader in the right cell
Dim lblHeaderPos As New TableLayoutPanelCellPosition(column, row)
TableLayoutPanel1.SetCellPosition(lblHeader, lblHeaderPos)
TableLayoutPanel1.Controls.Add(lblHeader)
End Sub
Let me know if you facing any problems.
Also if you don't know how many rows you will add, put the tableLyoutPanel inside a panel. Make the panel's property AutoScroll=True and then you can add infinite number of new rows.