Creating same size cells with TableLayoutPanel - c#

I'm trying to use the TableLayoutPanel in C# to fill out a Form. The TableLayoutPanel should hold 10x10 panels that all have the same size (by percentage).
Though I don't seem to get it to work for either the last row or the last column.
allPanel.RowCount = 10;
allPanel.ColumnCount = 10;
allPanel.Padding = 10;
allPanel.BackColor = Color.Green;
allPanel.AutoSize = true;
allPanel.Dock = DockStyle.Fill;
allPanel.RowStyles.Clear();
allPanel.ColumnStyles.Clear();
windowsForm.Controls.Add(allPanel);
for (int i = 0; i < 10; i++) {
allPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 10));
}
for (int i = 0; i < 10; i++) {
allPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 10));
}
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 10; ++j) {
boardTiles[i, j] = new Panel();
boardTiles[i, j].BackColor = Color.White;
allPanel.Controls.Add(boardTiles[i, j], i, j);
}
}
The result looks as follows:

The Cells' dimensions are integers; so for the layout to work you need to make sure that the net area of the TLP in in fact divisible by the number of cells you want it to contain.
The net area is the ClientSize minus the Padding.
So with a Padding of 10 around all sides you need a size of (n * w + 20, m * h + 20) for n x m cells of Width w and Height h.
Since you want to fill a container you need to either:
control the container size to match the formula
or compute the Padding so that it corrects for the integer divison errors
Here is a function to compute the correct Padding:
Padding GetCorrectionPadding(TableLayoutPanel TLP, int minimumPadding)
{
int minPad = minimumPadding;
Rectangle netRect = TLP.ClientRectangle;
netRect.Inflate(-minPad, -minPad);
int w = netRect.Width / TLP.ColumnCount;
int h = netRect.Height / TLP.RowCount;
int deltaX = (netRect.Width - w * TLP.ColumnCount) / 2;
int deltaY = (netRect.Height - h * TLP.RowCount) / 2;
int OddX = (netRect.Width - w * TLP.ColumnCount) % 2;
int OddY = (netRect.Height - h * TLP.RowCount) % 2;
return new Padding(minPad + deltaX, minPad + deltaY,
minPad + deltaX + OddX, minPad + deltaY + OddY);
}
Note that the code..
assumes the the TLP is already filled
assumes some value for the minimum Padding you want. Since we need up to n-1 pixels to do the correction the horizontal and vertical paddings may differ by half of that, in your case by up to 4 or 5 pixels.
Here is how you could call it:
allPanel.Padding = GetCorrectionPadding(allPanel, 5);
If you want to avoid this you need to go for option one, i.e. make sure the container has a suitable size!
The correction Padding will need to be applied again after each resizing, of course..

I don't like TableLayoutPanel. I can achieve same with code below. The number of properties on a TableLayoutPanel are limited and I often find I need additional properties. You can inherit any class (I used buttons). I place buttons on a form but you can also place the buttons on a standard Panel so the panel can be moved on a form and all the buttons move together.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Buttons
{
public partial class Form1 : Form
{
const int ROWS = 5;
const int COLS = 10;
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
}
public void Form1_Load(object sender, EventArgs e)
{
new MyButton(ROWS, COLS, this);
}
}
public class MyButton : Button
{
const int WIDTH = 50;
const int HEIGHT = 50;
const int SPACE = 5;
const int BORDER = 20;
public static List<List<MyButton>> buttons { get; set; }
public static List<MyButton> buttonList { get; set; }
public Form1 form1;
public int row { get; set; }
public int col { get; set; }
public MyButton()
{
}
public MyButton(int rows, int cols, Form1 form1)
{
buttons = new List<List<MyButton>>();
buttonList = new List<MyButton>();
this.form1 = form1;
for(int row = 0; row < rows; row++)
{
List<MyButton> newRow = new List<MyButton>();
buttons.Add(newRow);
for (int col = 0; col < cols; col++)
{
MyButton newButton = new MyButton();
newButton.Height = HEIGHT;
newButton.Width = WIDTH;
newButton.Top = row * (HEIGHT + SPACE) + BORDER;
newButton.Left = col * (WIDTH + SPACE) + BORDER;
newButton.row = row;
newButton.col = col;
newRow.Add(newButton);
buttonList.Add(newButton);
newButton.Click += new System.EventHandler(Button_Click);
form1.Controls.Add(newButton);
}
}
}
public void Button_Click(object sender, EventArgs e)
{
MyButton button = sender as MyButton;
MessageBox.Show(string.Format("Pressed Button Row {0} Column {1}", button.row, button.col));
}
}
}

Related

traverse a GUI button grid in c#

Recently I have created a sudoku solver program in c#, and now I am trying to create a visual GUI sudoku game based on my recent code. Now I have placed buttons inside a panel and saw that I can traverse the buttons in the panel like that :
foreach (Button b in this.gamePanel.Controls);
however, the traverse seems to be random, Is there any way to traverse these button in 2 for loops as I would traverse a matrix?
Thanks a lot :)
The buttons grid I created
Try following :
public partial class Form1 : Form
{
const int WIDTH = 60;
const int HEIGHT = 60;
const int SPACE = 3;
const int ROWS = 9;
const int COLS = 9;
List<List<Button>> buttons = new List<List<Button>>();
public Form1()
{
InitializeComponent();
for (int row = 0; row < ROWS; row++)
{
List<Button> newRow = new List<Button>();
buttons.Add(newRow);
for (int col = 0; col < COLS; col++)
{
Button newButton = new Button();
newButton.Width = WIDTH;
newButton.Height = HEIGHT;
newButton.Top = row * (HEIGHT + SPACE);
newButton.Left = col * (WIDTH + SPACE);
newButton.BackColor = (row / 3 + col / 3) % 2 == 0 ? Color.Red : Color.Black;
newRow.Add(newButton);
panel1.Controls.Add(newButton);
}
}
}
}

Dynamically creating dots/squares inside picturebox

I would like to be able to create X x Y number of boxes / circles / buttons inside a picturebox. EXACTLY like Windows Defrag tool.
I tried to create a layout and keep adding buttons or pictureboxes to it, but its extremely slow and after 200 or so pictureboxes it crashes, runs out of window handles or memory.
What's the alternative? Can you show me a simple piece of code to add boxes just like Defrag tool where I can easily access them like
box[x,y].Color = Green as my app makes progress?
Currently I have this:
private void ResetTableStyles()
{
boardPanel.Controls.Clear();
boardPanel.RowStyles.Clear();
boardPanel.ColumnStyles.Clear();
boardPanel.RowCount = Rows;
boardPanel.ColumnCount = Columns;
for (int i = 0; i < Rows; i++)
{
boardPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100f));
}
for (int j = 0; j < Columns; j++)
{
boardPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f));
}
}
private void CreateButtons()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var button = new PictureBox
{
BackColor = Color.White,
Dock = DockStyle.Fill,
Margin = Padding.Empty,
Tag = new Point(i, j),
BackgroundImageLayout = ImageLayout.Stretch
};
//button.MouseDown += button_MouseDown;
boardPanel.Controls.Add(button, j, i);
}
}
}
which as I said doesn't work, after sometime it just crashes and it takes very long time.
Perhaps someone has a better answer, but you could use a Panel as the canvas and handle the Paint event to draw colored rectangles onto the Panel. You could then use the mouse events, such as MouseMove, to figure out which cell you're on.
Here is a super-simple proof of concept.
// Data.cs
namespace WindowsFormsApplication
{
public class Data
{
public int State { get; set; }
public string Tip { get; set; }
}
}
// Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var rng = new Random();
_data = new Data[30 * 30];
for (int i = 0; i < _data.Length; i++)
{
_data[i] = new Data
{
State = rng.Next(0, 3),
Tip = $"Data at index: {i}"
};
}
}
private Data[] _data;
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (var brush = new SolidBrush(Color.Gray))
using (var buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics,
new Rectangle(0, 0, 450, 450)))
{
for (int x = 0; x < 30; x++)
{
for (int y = 0; y < 30; y++)
{
int dataIdx = (y * 30) + x;
Data data = _data[dataIdx];
if (data.State == 1)
{
brush.Color = Color.Blue;
}
else if (data.State == 2)
{
brush.Color = Color.Red;
}
else
{
brush.Color = Color.Gray;
}
buffer.Graphics.FillRectangle(brush, x * 15, y * 15, 15, 15);
buffer.Graphics.DrawLine(Pens.Black, 0, y * 15, 450, y * 15); //Gridline
}
buffer.Graphics.DrawLine(Pens.Black, x * 15, 0, x * 15, 450); //Gridline
}
buffer.Render(e.Graphics);
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
var point = e.Location;
int x = point.X / 15;
int y = point.Y / 15;
int dataIdx = (y * 30) + x;
System.Diagnostics.Debug.WriteLine(_data[dataIdx].Tip);
}
}
}
The Data class represents the model behind each segment on the panel and is pretty straight forward.
Form1 has a single control placed on it: a Panel named panel1 with a size of 450 x 450. Each cell on the panel will be 15 x 15, so we have 30 columns and 30 rows. In Form1's constructor, we initialize the Data that will be used to draw the panel.
Inside of Form1.panel1_Paint we iterate through the cells and look up the instance of Data for the given cell. Then, based on the State, set a color for the cell and draw a rectangle of that color. This is done on a buffer so the panel doesn't flicker when painting.
Inside of Form1.panel1_MouseMove we use the location of the mouse pointer relative to panel1 to figure out which cell the mouse is over, get the instance of Data for that cell, and display the Tip in the debug window.
You might be able to take this and build upon it, perhaps with a custom UserControl so you can easily access each individual cell with the column and row... your box[x, y].Color = Colors.Green example above.

Winforms FlowLayoutPanel remove unneeded spaces with autosize controls

I have FlowLayoutPanel with
AutoScroll = True
FlowDirection = LeftToRight
WrapContents = True
Added controls dynamically have same Width but AutoSize in Height. So the panel will be like this, which has vertical spaces between items. As the height of row managed by the greatest height of controls. So I want to remove these unneeded spaces, and the final result will be like this.
If there's no way to do it with FlowLayoutPanel, What's the proper idea to done it perfectly ?
Its a matrix and should be treated like a matrix.
my opinion is that Panel is more appropriate than a FlowLayoutpanel here.
please see my suggestion and output to achieve such a behavior.
clarification: this code needs improvements to be adapted to all possible cases but you can learn from it the basic idea how to deal with such problem.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Example();
}
// space beetween controls (top and right)
public int MarginSpace = 8;
// first element location
public Point StartPoint = new Point(10, 10);
private void Example()
{
var fixesWidth = 70;
List<Label> randomLables = new List<Label>();
Random rand = new Random();
// generate lables with random heights
for (int i = 1; i < 10; i++)
{
Label lr = new Label();
var randheight = rand.Next(60, 120);
lr.Size = new Size(fixesWidth, randheight);
lr.Text = i.ToString();
lr.BackColor = Color.Black;
lr.ForeColor = Color.White;
randomLables.Add(lr);
}
// check how many elements in one "column" (possible also to add right+left margin)
var cols = panel1.Width / fixesWidth;
// create matrix object to get locations of each label
MyMatrix m = new MyMatrix(cols, randomLables.Count, 15, 70, StartPoint);
m.SetMatrix(randomLables);
int counter = 0;
// pupulate all lables with the points from MyMatrix object
foreach (Point p in m.pointsMatrix)
{
randomLables[counter].Location = p;
panel1.Controls.Add(randomLables[counter]);
counter++;
}
}
}
class MyMatrix
{
private int Rows;
private int TotalElements;
private int Cols;
private int Margin;
private int ElementWidth;
private Point StartPoint;
public MyMatrix(int cols, int totalelements, int margin, int elementwidth, Point startingpoint)
{
this.Cols = cols;
this.TotalElements = totalelements;
this.Margin = margin;
this.ElementWidth = elementwidth;
this.StartPoint = startingpoint;
// calculate number of rows
Rows = totalelements / cols;
}
public List<Point> pointsMatrix = new List<Point>();
int cellCounter = 0;
public void SetMatrix(List<Label> Labels)
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Cols; j++)
{
var x = StartPoint.X + j * (Margin + ElementWidth);
var y = StartPoint.Y;
if (cellCounter >= Cols)
{
// find the parallel cell in the row above
y = pointsMatrix[cellCounter - Cols].Y + Labels[cellCounter - Cols].Height + Margin;
}
else
{
// do nothing it is first row
}
Point p = new Point(x, y);
pointsMatrix.Add(p);
cellCounter += 1;
}
}
}
}
Output:

How to (create and) add components to a Table Layout?

I am in the process of creating a chess game in C#. Coming from a Java-Swing environment I made a standard function that creates a field 8x8 and gives it basic attributes.
Board = new Label[8, 8];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
Board[i, j] = new System.Windows.Forms.Label();
Board[i, j].Location = new Point(i * 50, j * 50);
Board[i, j].Size = new System.Drawing.Size(50, 50);
Board[i, j].Visible = true;
if ((i + j) % 2 == 0) // Color decision
{
Board[i, j].BackColor = Color.Black;
}
else {
Board[i, j].BackColor = Color.White;
}
this.Controls.Add(Board[i, j]);
}
}
Now there are two additional arrays which hold the abc's and 123's of the outer edge of the chess board (so that you can enter a move like - "Knight to E3").
I've managed to add all components onto the screen but they currently overlap each other. I was thinking of creating a 9x9 "grid-layout" and adding all components to it.
From Java I am used to simple commands like:
GridLayout gl = new Gridlayout(3,3);
this.setLayout(gl);
And then all added elements get automatically put into the grid.
After many hours of research, I cannot find anything similar in C#. Playing with the TableLayout only causes more problems that solutions.
My question is how to implement a (grid) layout and add all my labels to it?
I apologize in advanced for not posting any of my Layout code, but like I said it is just a mess and does nothing that it should.
Thank you :)
I guess in Win Forms it works a little bit different.
You need to create a TableLayoutPanel, then access TableLayoutPanel.Controls and add new controls one by one by calling Control.ControlCollection.Add method.
var panel = new TableLayoutPanel();
panel.ColumnCount = 3;
panel.RowCount = 4;
for(int i = 0; i< panel.ColumnCount; ++i)
panel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
for (int i = 0; i < panel.RowCount; ++i)
panel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
panel.Dock = DockStyle.Fill;
this.Controls.Add(panel);
for (int c = 0; c < 3; ++c)
{
for (int r = 0; r < 4; ++r)
{
var btn = new Button();
btn.Text = (c+r).ToString();
btn.Dock = DockStyle.Fill;
panel.Controls.Add(btn, c, r);
}
}
I like creating my own class that inherits a form control like code below. You can add your own properties like row and col or with a chess board an image which is a picture of the piece. See code below. You can add the code below to a layout panel instead of adding to a form like I did. You can create a space on the board which inherits a rectangle and then add an image to your custom rectangle which would replace the button class below.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MyButton myButton = new MyButton(this);
}
}
public class MyButton : Button
{
public static List<List<MyButton>> board { get; set; }
public static List<MyButton> buttons { get; set; }
const int WIDTH = 10;
const int HEIGTH = 10;
const int SPACE = 5;
const int ROWS = 10;
const int COLS = 20;
public int row { get; set; }
public int col { get; set; }
public MyButton()
{
}
public MyButton(Form1 form1)
{
board = new List<List<MyButton>>();
buttons = new List<MyButton>();
for (int _row = 0; _row < ROWS; _row++)
{
List<MyButton> newRow = new List<MyButton>();
board.Add(newRow);
for (int _col = 0; _col < COLS; _col++)
{
MyButton newButton = new MyButton();
newButton.row = _row;
newButton.col = _col;
newButton.Width = WIDTH;
newButton.Height = HEIGTH;
newButton.Top = _row * (HEIGTH + SPACE);
newButton.Left = _col * (WIDTH + SPACE);
form1.Controls.Add(newButton);
newRow.Add(newButton);
buttons.Add(newButton);
}
}
}
}
}

Performance issue when adding controls to a panel in C#

I am creating a panel and then adding some labels/buttons to it to form a grid. The issue is that if I add more than say 25x25 items to the panel, there is a terrible performance hit. I can resize the form ok but when I scroll the panel to see all the labels the program lags, the labels/buttons tear or flicker, and sometimes it can make the program unresponsive. I have tried adding the controls to a "DoubleBufferedPanel" that I created. This seems to have no effect. What else could I do? Sorry for such a large code listing. I didn't want to waste anyone's time.
namespace GridTest
{
public partial class Form1 : Form
{
private const int COUNT = 50;
private const int SIZE = 50;
private Button[,] buttons = new Button[COUNT, COUNT];
private GridPanel pnlGrid;
public Form1()
{
InitializeComponent();
pnlGrid = new GridPanel();
pnlGrid.AutoScroll = true;
pnlGrid.Dock = DockStyle.Fill;
pnlGrid.BackColor = Color.Black;
this.Controls.Add(pnlGrid);
}
private void Form1_Load(object sender, EventArgs e)
{
int x = 0;
int y = 0;
int offset = 1;
for (int i = 0; i < COUNT; i++)
{
for (int j = 0; j < COUNT; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Size = new Size(SIZE, SIZE);
buttons[i, j].Location = new Point(x, y);
buttons[i, j].BackColor = Color.White;
pnlGrid.Controls.Add(buttons[i, j]);
x = x + SIZE + offset;
}
x = 0;
y = y + SIZE + offset;
}
}
}
}
Also, the GridPanel class:
namespace GridTest
{
public class GridPanel : Panel
{
public GridPanel()
: base()
{
this.DoubleBuffered = true;
this.ResizeRedraw = false;
}
}
}
If you must add controls at run time, based on some dynamic or changing value, you might want to consider creating an image on the fly, and capturing mouse click events on its picturebox. This would be much quicker and only have one control to draw rather than hundreds. You would lose some button functionality such as the click animation and other automatic properties and events; but you could recreate most of those in the generation of the image.
This is a technique I use to offer users the ability to turn on and off individual devices among a pool of thousands, when the location in a 2-dimensional space matters. If the arrangement of the buttons is unimportant, you might be better offering a list of items in a listview or combobox, or as other answers suggest, a datagridview with button columns.
EDIT:
An example showing how to add a graphic with virtual buttons. Very basic implementation, but hopefully you will get the idea:
First, some initial variables as preferences:
int GraphicWidth = 300;
int GraphicHeight = 100;
int ButtonWidth = 60;
int ButtonHeight = 20;
Font ButtonFont = new Font("Arial", 10F);
Pen ButtonBorderColor = new Pen(Color.Black);
Brush ButtonTextColor = new SolidBrush(Color.Black);
Generating the image:
Bitmap ControlImage = new Bitmap(GraphicWidth, GraphicHeight);
using (Graphics g = Graphics.FromImage(ControlImage))
{
g.Clear(Color.White);
for (int x = 0; x < GraphicWidth; x += ButtonWidth)
for (int y = 0; y < GraphicHeight; y += ButtonHeight)
{
g.DrawRectangle(ButtonBorderColor, x, y, ButtonWidth, ButtonHeight);
string ButtonLabel = ((GraphicWidth / ButtonWidth) * (y / ButtonHeight) + x / ButtonWidth).ToString();
SizeF ButtonLabelSize = g.MeasureString(ButtonLabel, ButtonFont);
g.DrawString(ButtonLabel, ButtonFont, ButtonTextColor, x + (ButtonWidth/2) - (ButtonLabelSize.Width / 2), y + (ButtonHeight/2)-(ButtonLabelSize.Height / 2));
}
}
pictureBox1.Image = ControlImage;
And responding to the Click event of the pictureBox:
// Determine which "button" was clicked
MouseEventArgs em = (MouseEventArgs)e;
Point ClickLocation = new Point(em.X, em.Y);
int ButtonNumber = (GraphicWidth / ButtonWidth) * (ClickLocation.Y / ButtonHeight) + (ClickLocation.X / ButtonWidth);
MessageBox.Show(ButtonNumber.ToString());
I think you won't be able to prevent flickering having 625 buttons (btw, windows) on the panel. If such layout is mandatory for you, try to use the DataGridView, bind it to a fake data source containing the required number of columns and rows and create DataGridViewButtonColumns in it. This should work much more better then your current result ...

Categories

Resources