Winforms FlowLayoutPanel remove unneeded spaces with autosize controls - c#

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:

Related

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.

Creating same size cells with TableLayoutPanel

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));
}
}
}

c# gomoku game label array

I am trying to make a simple five in a row (gomoku) game for two players using windows forms and c#. I put a picturebox with a picture and stretched it out on the form. Now I want to put labels at all the intersections on the picture board so a user can click them and change their background color to black or white.
How can I make the labels created clickable on the form?
public partial class Form1 : Form
{
int labelCount = 0;
int iteration = 0;
public Form1()
{
InitializeComponent();
Label[] board = new Label[361];
for (int i = 0; i < 361; i++)
{
board[i] = new Label
{
Name = "label" + i,
Height = 55,
Width = 55,
MinimumSize = new Size(55, 55),
Text = "label " + i
};
}
int x = 0;
int y = 0;
foreach (var Label in board)
{
if (x >= 580)
{
x = 0;
y = y + Label.Height + 55;
}
Label.Location = new Point(x, y);
this.Controls.Add(Label);
x += Label.Width;
}
}
}
Should I make a one-dimensional [361] or two-dimensional array[{A,1}, {A,2}....{D,1}] to easily check for a winner? How can I connect it to the created labels so the array data corresponds to the objects on the board?
Well Sorry If don`t understand your question. For the Q.1 to add 361 labels you can try the code below. I hope it will help you.
public int x = 0;
public int y = 0;
private Label[] moku = new Label[361];
private void Form1_Load(object sender, EventArgs e)
{
try
{
for (int i = 0; i < 361; i++)
{
moku[i] = new Label();
moku[i].Parent = pictureBox1;//make the picturebox parent
moku[i].Location = new Point(x, y);
moku[i].Text = "O";
moku[i].Name = "moku" + i;
moku[i].BackColor = Color.Transparent;
pictureBox1.Controls.Add(moku[i]);
y += 55;
if (y >= 361) { x += 55; y = 0; x+=55; }
}
}catch(Exception er)
{
MessageBox.Show(er.ToString());
}
}
I prefer using a 2D array because it's easier if you want to check the surrounding boxes.
Form design:
Full source:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public enum Player
{
Empty = 0,
White,
Black
}
public partial class Form1 : Form
{
// initialize board of 5x5
private Player[,] board = new Player[5, 5];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
DrawBoard();
}
private void DrawBoard()
{
for (var i = 0; i <= board.GetUpperBound(0); i++)
{
for (var j = 0; j <= board.GetUpperBound(1); j++)
{
// for name and text
var name = string.Format("{0}, {1}", i, j);
var label = new Label()
{
Name = name, // name of label
Size = new Size(55, 55),
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(i * 55, j * 55), // location depends on iteration
Text = name
};
label.Click += ClickLabel; // subscribe the Click event handler
pictureBox1.Controls.Add(label); // add label to a container
}
}
}
// this event handler will handle all the labels click event
private void ClickLabel(object sender, EventArgs e)
{
var label = (Label)sender; // this is the label that you click
var x = Convert.ToInt32(label.Name.Split(',')[0]);
var y = Convert.ToInt32(label.Name.Split(',')[1]);
// change the color
if (radPlayerBlack.Checked)
{
// Player Black
label.ForeColor = Color.White;
label.BackColor = Color.Black;
board[x, y] = Player.Black;
}
else
{
// Player White
label.ForeColor = Color.Black;
label.BackColor = Color.White;
board[x, y] = Player.White;
}
}
}
}
You can check the value of the 2D array for black or white. Here's the value when I QuickWatch it in Visual Studio.

WPF Window don't display element inherited from Canvas

I want that all my actions for drawing graph were in my control, inherited from Canvas. But window don't display MyCanvas. I don't know why.
class MyCanvas : Canvas
{
private Double XTimeScale;
private Double YAmpSacle;
private Double YTopLimit;
private Double YBotLimit;
private List<Point> DotsGraph;
public MyCanvas(Double XTimeScale,
Double YAmpSacle,
Double YTopLimit,
Double YBotLimit)
{
this.XTimeScale = XTimeScale;
this.YAmpSacle = YAmpSacle;
this.YTopLimit = YTopLimit;
this.YBotLimit = YBotLimit;
this.Height = (YTopLimit + YBotLimit);
}
public List<Line> DrawNet(Double Width, Double Height)
{
List<Line> temp = new List<Line>();
SolidColorBrush brush = new SolidColorBrush();
brush.Color = Colors.Gray;
for (int i = 0; i < Width; i+= 10)
{
Line Y = new Line();
Y.Stroke = brush;
Y.StrokeThickness = 1;
Y.Y1 = 0;
Y.Y2 = Height;
Y.X1 = i;
Y.X2 = i;
temp.Add(Y);
}
for (int j = 0; j < Height; j += 10)
{
Line X = new Line();
X.Stroke = brush;
X.StrokeThickness = 1;
X.X1 = 0;
X.X2 = Width;
X.Y1 = j;
X.Y2 = j;
temp.Add(X);
}
foreach (var t in temp)
this.Children.Add(t);
return temp;
}
}
And the class of windows where MyCanvas should be displayed. I added it to Grid:
public partial class ShowCanvas : Window
{
public ShowCanvas()
{
InitializeComponent();
MyCanvas EAP = new MyCanvas(300, 300, 300, 300);
Base.Children.Add(EAP); // Base is Grid on window ShowCanvas
}
}
Your window did display your canvas, but your canvas didn't have any children. Call DrawNet method.
You can use an open-source program called "Snoop" to navigate through the visual tree and see properties of visuals

Setting a size for a label border

I made a grid of 9 by 9 labels and each label has a border. After each 3 labels in a row/column I want the border to be thicker then the previous ones. I can't find a way to add this size of that border.
I searched on google, but couldn't find anything useful.
Can anyone help me?
private void AddNodesToGrid()
{
pnlGrid.Controls.Clear();
rooster = new NewLabel[9, 9];
int Xpos = 0;
int Ypos = 0;
for (int I = 0; I < 9; I++)
{
for (int T = 0; T < 9; T++)
{
rooster[I, T] = new NewLabel(new Node());
rooster[I, T].Left = Xpos;
rooster[I, T].Top = Ypos;
rooster[I, T].Width = 30;
rooster[I, T].Height = 30;
rooster[I, T].BorderStyle = BorderStyle.FixedSingle;
rooster[I, T].TextAlign = ContentAlignment.MiddleCenter;
pnlGrid.Controls.Add(rooster[I, T]);
Xpos += 30;
}
Xpos = 0;
Ypos += 30;
}
}
If it were me, I preferred to draw my own table. But if you need to use your labels, I advise you to paint the borders yourslef:
public class NewLabel : Label
{
//...
private int _borderWidth = 1;
public int BorderWidth
{
get { return _borderWidth; }
set
{
_borderWidth = value;
Invalidate();
}
}
private Color _borderColor = Color.Black;
public Color BorderColor
{
get { return _borderColor; }
set
{
_borderColor = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
int xy = 0;
int width = this.ClientSize.Width;
int height = this.ClientSize.Height;
Pen pen = new Pen(_borderColor);
for (int i = 0; i < _borderWidth; i++)
e.Graphics.DrawRectangle(pen, xy + i, xy + i, width - (i << 1) - 1, height - (i << 1) - 1);
}
}
Now your NewLabel class has BorderWidth and BorderColor properties that you can set.
(Note: The way I used to draw the border is the fastest one. Creating a pen with required width does not work well because GDI+ puts the center of the line on the specified coordinates.)
a better way to accomplish this is to use a nested TableLayoutPanel. Create it from the designer and place your labels inside. Steps :
Place a 3x3 TableLayoutPanel (Parent Panel).
Place a 3x3 TableLayoutPanel (Child Panels) in each cell of the parent panel.
set the CellBorderStyle to Single for parent table and child tables.
set the Margin for child tables to 0,0,0,0.
You will get this effect :

Categories

Resources