Make and change TableLayoutPanel at runtime in WinForms - vb.net

I would like to know how to make and change a TableLayoutPanel at runtime in VB.NET, WinForms.
I've had a look at the MSDN documentation but I can't seem to understand how to vary the number of columns/rows (ie create new ones) nor how to change the values of any of the cells.
My aim is to have a 4x4 grid that contains 16 labels, whose text comes from a multi-dimensional (4x4) integer array.
My current code is:
Dim table As New TableLayoutPanel
table.ColumnCount = 4
table.RowCount = 4
table.RowStyles.Add(New RowStyle(SizeType.Absolute, 8.0F))
This is based off the MSDN examples, but I'm not sure how to use the RowStyles.Add(several arguments) method. Can anyone explain it?

The following will create a TableLayoutPanel and all labels at run time. It is fully adjustable, in that you have have a 2 dimensional array of any size and it will display all values within that array. Using this code example should show you how to add rows and columns to a TableLayoutPanel dynamically at runtime.
Public Class Form1
Friend WithEvents TableLayout As TableLayoutPanel
Private DataArray(,) As Integer = New Integer(3, 3) {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.AutoSizeMode = Windows.Forms.AutoSizeMode.GrowAndShrink
Me.AutoSize = True
TableLayout = New TableLayoutPanel
With TableLayout
.Name = "tableLayout"
.Margin = New System.Windows.Forms.Padding(0, 0, 0, 0)
.ColumnCount = 0
.RowCount = 0
.Dock = DockStyle.Fill
.AutoSizeMode = Windows.Forms.AutoSizeMode.GrowAndShrink
.AutoSize = True
End With
Me.Controls.Add(TableLayout)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
For x = LBound(DataArray, 1) To UBound(DataArray, 1)
Me.TableLayout.ColumnCount += 1
Me.TableLayout.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
For y = LBound(DataArray, 2) To UBound(DataArray, 2)
If y = LBound(DataArray, 2) Then
Me.TableLayout.RowCount += 1
Me.TableLayout.RowStyles.Add(New ColumnStyle(SizeType.AutoSize))
End If
Dim lbl = New Label
With lbl
.Name = "lbl" & x & y
.TextAlign = ContentAlignment.MiddleCenter
.Text = "Value: " & DataArray.GetValue(x, y)
.Dock = DockStyle.Fill
.AutoSize = True
End With
Me.TableLayout.Controls.Add(lbl, y, x)
Next
Next
End Sub
End Class

I suggest to you to create a TableLayoutPanel using Designer and after that to check the auto generated code in Designer.cs (Designer.vb in your case) class. Here small example in C#:
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 4;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.Location = new System.Drawing.Point(252, 75);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 4;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(200, 100);
this.tableLayoutPanel1.TabIndex = 4;
To add Controls to your TableLayout use it Controls property. For example:
private void button2_Click(object sender, EventArgs e)
{
Label label = new Label();
label.Text = "Hello!";
tableLayoutPanel1.Controls.Add(label, 0, 0);
}
enter code here

Related

Is it possible to hide the header cell in a DataGridView for specific columns?

I have a DataGridView which is populated from a DataTable. I then supplement this with additional DataGridViewImageColumn which will ultimately function as buttons for manipulating the data on any given row.
What I want to know is, is it possible to hide the header cells for these image columns, but still retain them for the "data" columns (the image columns are the right-most columns in the grid) Either natively (the .Visible property of the individual image columns seems to be read-only?) or with some kind of funky workaround when painting the cells?
The buttons are relatively small but adding column headers (e.g. "Edit", "Delete" etc.) widens the columns unnecessarily and the images are (meant to be) self-explanatory as to what each one achieves. I also want to visually distinguish the data columns from the "action" columns. Yes, this is a purely aesthetic decision on my part!
Here is the code I am currently using to generate the DataGridView :
Dim myBindingSource As New BindingSource
myBindingSource.DataSource = myDataTable
myBindingSource.Filter = myFilter
With myDataGridView
.DataSource = myBindingSource
.AutoResizeColumns()
.AutoResizeRows()
.RowHeadersVisible = False
.AllowUserToAddRows = False
.Enabled = True
btnEdit = New DataGridViewImageColumn With {.Image = icoEdit, .Width = 30}
btnEdit.DefaultCellStyle.Padding = New Padding(1, 1, 1, 1)
.Columns.Insert(.Columns.Count, btnEdit)
btnDelete = New DataGridViewImageColumn With {.Image = icoDelete, .Width = 30}
btnDelete.DefaultCellStyle.Padding = New Padding(1, 1, 1, 1)
.Columns.Insert(.Columns.Count, btnDelete)
btnCopy = New DataGridViewImageColumn With {.Image = icoCopy, .Width = 30}
btnCopy.DefaultCellStyle.Padding = New Padding(1, 1, 1, 1)
.Columns.Insert(.Columns.Count, btnCopy)
btnRestore = New DataGridViewImageColumn With {.Image = icoRestore, .Width = 30}
btnRestore.DefaultCellStyle.Padding = New Padding(1, 1, 1, 1)
.Columns.Insert(.Columns.Count, btnRestore)
End With
I'd prefer to have the header cells for these columns as "dead space", similar to the surrounding areas outside the grid...
Possible.
VB.NET
Private Sub myDataGridView_CellPainting(sender As Object,
e As DataGridViewCellPaintingEventArgs) Handles myDataGridView.CellPainting
Dim tarCols = {1, 2}
If e.RowIndex = -1 AndAlso tarCols.Contains(e.ColumnIndex) Then
e.Graphics.FillRectangle(SystemBrushes.AppWorkspace, e.CellBounds)
e.Handled = True
End If
End Sub
C#
private void myDataGridView_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
var tarCols = new[] { 1, 2 };
if (e.RowIndex == -1 && tarCols.Contains(e.ColumnIndex))
{
e.Graphics.FillRectangle(SystemBrushes.AppWorkspace, e.CellBounds);
e.Handled = true;
}
}
In case you have a different .BackgroundColor:
VB.NET
Private Sub myDataGridView_CellPainting(sender As Object,
e As DataGridViewCellPaintingEventArgs) Handles myDataGridView.CellPainting
Dim tarCols = {1, 2}
If e.RowIndex = -1 AndAlso tarCols.Contains(e.ColumnIndex) Then
Using br = New SolidBrush(myDataGridView.BackgroundColor)
e.Graphics.FillRectangle(br, e.CellBounds)
End Using
e.Handled = True
End If
End Sub
C#
private void myDataGridView_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
var tarCols = new[] { 1, 2 };
if (e.RowIndex == -1 && tarCols.Contains(e.ColumnIndex))
{
using (var br = new SolidBrush(myDataGridView.BackgroundColor))
e.Graphics.FillRectangle(br, e.CellBounds);
e.Handled = true;
}
}

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)

select a different value to multiple comboboxes with the same datasource

It seems as the same problem like in many other posts here and elsewhere.
But everything I tried so far... failed glamorously.
Let me explain:
In the first snippet I populate datagridview with one blank row and create a combobox for each column of this same datagridview. All comboboxes are bind to the same bindingsource. The code is written in this way to allow creating as many comboboxes as there are columns in the datagridview. The number of columns may be different each time.
The problem is that when I change value of the first combobox all others are changed to the same value. And that is not what I'd like to achieve. Now here's what I've tried so far.
CODE1
Private Sub BT_paste_data_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_paste_data.Click
DGV.Rows.Add()
Dim rect As Rectangle
For i = 0 To DGV.Columns.Count - 1
Dim cbc as combobox
cbc = New ComboBox
cbc.Name = i.ToString
DGV.Controls.Add(cbc)
cbc.Visible = True
cbc.BringToFront()
cbc.DataSource = datastringsBindingSource
cbc.ValueMember = "id_data"
cbc.DisplayMember = "data"
cbc.SelectedItem = 9
cbc.Text = "Don't add"
cbc.FlatStyle = FlatStyle.Flat
cbc.BackColor = SystemColors.Menu
cbc.ForeColor = Color.FromArgb(64, 64, 64)
cbc.Font = New Font(DGV.Font, FontHeight = 8.25)
rect = DGV.GetCellDisplayRectangle(i, 0, False)
cbc.Left = rect.Left
cbc.Top = rect.Top
DGV = 20
Next
End If
End Sub
The code above produces following...
picture of comboboxes after load
and when I select a different item from the list in the first combobox all others are also changed.
So I searched the web and found that same bindingsource could be the problem.
So I changed the code some to bind comboboxes to a different bindingsource each time.
CODE2
Private Sub BT_paste_data_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_paste_data.Click
DGV.Rows.Add()
Dim rect As Rectangle
For i = 0 To DGV.Columns.Count - 1
Dim cbc as combobox
cbc = New ComboBox
cbc.Name = i.ToString
DGV.Controls.Add(cbc)
cbc.Visible = True
cbc.BringToFront()
'following two lines were added and the third changed accordingly
Dim bs As New BindingSource
bs = datastringsBindingSource
cbc.DataSource = bs
cbc.DataSource = bs
cbc.ValueMember = "id_data"
cbc.DisplayMember = "data"
cbc.SelectedItem = 9
cbc.Text = "Dont add"
cbc.FlatStyle = FlatStyle.Flat
cbc.BackColor = SystemColors.Menu
cbc.ForeColor = Color.FromArgb(64, 64, 64)
cbc.Font = New Font(DGV.Font, FontHeight = 8.25)
rect = DGV.GetCellDisplayRectangle(i, 0, False)
cbc.Left = rect.Left
cbc.Top = rect.Top
DGV = 20
Next
End If
End Sub
I get same result as with CODE1. Comboboxes are created fine but when value in one of them is changed all of them get changed accordingly.
So I thought that perhaps creating all comboboxes like 'cbc' (although they have different names (0,1,2,3) could be the problem.
At this point I'm opened for any suggestions.
following the suggestion from fabio I changed a part of the CODE2:
Private Sub BT_paste_data_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_paste_data.Click
DGV.Rows.Add()
Dim rect As Rectangle
For i = 0 To DGV.Columns.Count - 1
Dim cbc as combobox
cbc = New ComboBox
cbc.Name = i.ToString
DGV.Controls.Add(cbc)
cbc.Visible = True
cbc.BringToFront()
'following two lines were added and the third changed accordingly
Dim bs As New BindingSource
bs = datastringsBindingSource
'next line was suggested by fabio but the result is still the same
cbc.BindingContext = New BindingContext()
cbc.DataSource = bs
cbc.ValueMember = "id_data"
cbc.DisplayMember = "data"
cbc.SelectedItem = 9
cbc.Text = "Dont add"
cbc.FlatStyle = FlatStyle.Flat
cbc.BackColor = SystemColors.Menu
cbc.ForeColor = Color.FromArgb(64, 64, 64)
cbc.Font = New Font(DGV.Font, FontHeight = 8.25)
rect = DGV.GetCellDisplayRectangle(i, 0, False)
cbc.Left = rect.Left
cbc.Top = rect.Top
DGV = 20
Next
End If
End Sub
OK I got it.
Since 'new bindingcontext' didn't work and I was now more and more convinced that duplicating bindingsource would solve the problem, I checked for solutions on bindingsource duplication and found out following line should be changed. So...
Dim bs As new Bindingsource
should become:
Dim bs As new Bindingsource(datastringsBindingSource.Datasource, datastringsBindingSource.DataMember)
Therefore my final working solution is for now:
Private Sub BT_paste_data_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_paste_data.Click
DGV.Rows.Add()
Dim rect As Rectangle
For i = 0 To DGV.Columns.Count - 1
Dim cbc as combobox
cbc = New ComboBox
cbc.Name = i.ToString
DGV.Controls.Add(cbc)
cbc.Visible = True
cbc.BringToFront()
'here is the line that was changed
Dim bs As new Bindingsource(datastringsBindingSource.Datasource, datastringsBindingSource.DataMember)
bs = datastringsBindingSource
'next line was suggested by fabio but the result is still the same
'cbc.BindingContext = New BindingContext() - sorry fabio, this line seems to be irelevant
cbc.DataSource = bs
cbc.ValueMember = "id_data"
cbc.DisplayMember = "data"
cbc.SelectedItem = 9
cbc.Text = "Dont add"
cbc.FlatStyle = FlatStyle.Flat
cbc.BackColor = SystemColors.Menu
cbc.ForeColor = Color.FromArgb(64, 64, 64)
cbc.Font = New Font(DGV.Font, FontHeight = 8.25)
rect = DGV.GetCellDisplayRectangle(i, 0, False)
cbc.Left = rect.Left
cbc.Top = rect.Top
DGV = 20
Next
End Sub

Handling events of objects that were created in lists

I'm making a Scrabble game in Visual Basic. In my project, I created a list of labels that will make up my "tiles" of the game board. I'm just wondering how I would handle events for labels in the list, because they aren't objects that I created in the designer. (ex: Click, Hover events)
Here is the code that creates the grid of labels:
Dim labels As New List(Of Label)
For i = 0 To 10
For y = 0 To 10
Dim temp As New Label
With temp
Dim nfont As New Font("Fixedsys Excelsior 2.00", 15)
.Name = Str(i)
.AllowDrop = True
.BackColor = Color.White
.Location = New Point(y * 55 + 465, i * 45)
.Size = New System.Drawing.Size(50, 50)
.Visible = True
.Image = Image.FromFile("E:\Scrabble\Images\Blank.png")
.CreateControl()
.TextAlign = ContentAlignment.MiddleCenter
.ForeColor = Color.LimeGreen
.Font = nfont
End With
Me.Controls.Add(temp)
labels.Add(temp)
Next
Next
You would add the handler before adding the label in the list:
AddHandler temp.Click, AddressOf LabelClickHandler
Me.Controls.Add(temp)
labels.Add(temp)

Adding checkbox to datagridview column header, not aligning properly

Im trying to add a checkbox to a specific datagridview column header, I found some code online to help but it's not aligning properly and I'm not really sure how to fix it.
Below is an image of the problem and the code, any help would be greatly appreciated!
P.S. I think it might be something to do with properties but I've played around with them but not been successful.
Private checkboxHeader231 As CheckBox
Private Sub show_chkBox()
Dim rect As Rectangle = DataGridView1.GetCellDisplayRectangle(columnIndexOfCheckBox, -1, True)
' set checkbox header to center of header cell. +1 pixel to position
rect.Y = 3
rect.X = rect.Location.X + 8 + (rect.Width / 4)
checkboxHeader231 = New CheckBox()
With checkboxHeader231
.BackColor = Color.Transparent
End With
checkboxHeader231.Name = "checkboxHeader1"
checkboxHeader231.Size = New Size(18, 18)
checkboxHeader231.Location = rect.Location
AddHandler checkboxHeader231.CheckedChanged, AddressOf checkboxHeader231_CheckedChanged
DataGridView1.Controls.Add(checkboxHeader231)
End Sub
Private Sub checkboxHeader231_CheckedChanged(sender As System.Object, e As System.EventArgs)
Dim headerBox As CheckBox = DirectCast(DataGridView1.Controls.Find("checkboxHeader1", True)(0), CheckBox)
For Each row As DataGridViewRow In DataGridView1.Rows
row.Cells(columnIndexOfCheckBox).Value = headerBox.Checked
Next
End Sub
This is my first entry, but I think this is what youre looking for. I tested it and it worked on my datagrid. You were using the width for the rectangle, youll need it for the column width instead. I set the column header to 4, but you would replace the 4 with your column you want to use I put it in two ways, one with a four loop, the other just as single lines. Tell me if this worked for you:
Dim rect As Rectangle = DataGridView1.GetCellDisplayRectangle(4, -1, True) ' replace 4
rect.Y = 3
Dim sum = DataGridView1.Columns(0).Width
'for this area write a for loop to find the width of each column except for the last line which you manually do
'
'
'For i As Integer = 1 To 4 - 1 Step 1 ' replace 4
'sum = sum + DataGridView1.Columns(i).Width
'Next
sum = sum + DataGridView1.Columns(1).Width
sum = sum + DataGridView1.Columns(2).Width
sum = sum + DataGridView1.Columns(3).Width
' stop here and add the last line by hand here
sum = sum + (DataGridView1.Columns(4).Width / 2) + 35 ' used in both cases ' replace 4
rect.X = sum
checkboxHeader231 = New CheckBox()
With checkboxHeader231
.BackColor = Color.Transparent
End With
checkboxHeader231.Name = "checkboxHeader1"
checkboxHeader231.Size = New Size(18, 18)
checkboxHeader231.Location = rect.Location
AddHandler checkboxHeader231.CheckedChanged, AddressOf checkboxHeader231_CheckedChanged
DataGridView1.Controls.Add(checkboxHeader231)
Private headerBox As CheckBox
Private Sub show_checkBox()
Dim checkboxHeader As CheckBox = New CheckBox()
Dim rect As Rectangle = PendingApprovalServiceListingDataGridView.GetCellDisplayRectangle(4, -1, True)
rect.X = 20
rect.Y = 12
With checkboxHeader
.BackColor = Color.Transparent
End With
checkboxHeader.Name = "checkboxHeader"
checkboxHeader.Size = New Size(14, 14)
checkboxHeader.Location = rect.Location
AddHandler checkboxHeader.CheckedChanged, AddressOf checkboxHeader_CheckedChanged
PendingApprovalServiceListingDataGridView.Controls.Add(checkboxHeader)
End Sub
Private Sub checkboxHeader_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs)
headerBox = DirectCast(PendingApprovalServiceListingDataGridView.Controls.Find("checkboxHeader", True)(0), CheckBox)
For Each row As DataGridViewRow In PendingApprovalServiceListingDataGridView.Rows
row.Cells(0).Value = headerBox.Checked
Next
End Sub