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:
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));
}
}
}
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.
My program is pretty straight forward in concept - it allows a user to take scores during bowling tournaments, as well as showing the players the scores through a scoreboard.
There's a score sheet form in which they enter scores, and a scoreboard form that shows the scores to the players, by division. The scoreboard is run in a different thread than the main program.
The scoreboard is comprised of a TableLayoutPanel which I manipulate programmatically to represent a table of the scores to show. My issue with this is that it takes a long time (especially for a long list of players) for the table to render. The user experience of watching the table render itself leaves to be desired as well.
I tested the speed of rendering textboxes, labels, and picture boxes to lessen the load; textboxes won, so I changed the score labels to textboxes... but it's still not enough.
It's a lot to look through, but if anyone has any idea how I could further speed up the rendering of the scoreboard, I'd be all ears/eyes.
Here's my process broken down.
The calling method (called every 15 seconds by a timer):
private void switchBoard()
{
night = nights.GetNight(nightID);
NightDay = night.Night_Date.ToString("ddd");
//set the id of the division to show
dtDivisions = scoreBoard.roll_display(dtDivisions);
//get the row that is set to show
DataRow[] drs = dtDivisions.Select("showing = 1");
DataRow dr = drs[0];
//update the title
lbl_title.Top = 30;
lbl_title.Text = (string)dr["title"] + "'s Qualifying - " + NightDay;
lbl_title.Width = this.Width;
lbl_title.TextAlign = ContentAlignment.MiddleCenter;
//SET UP THE TABLE
//get number of columns (games) for selected night
int Cols = games.GetCountGamesForTourNightByDivision(tourID, nightID, scoreBoard.ShowDivision) + 3; //ACCOUNT FOR HEADER COLS, RANK AND TOTALS
//get number of rows (players) for selected night
int Rows = players.GetCountPlayersForTourNightByDivision(TourID, nightID, scoreBoard.ShowDivision) + 1; //ACCOUNT FOR HEADER ROWS
//generate the table
GenerateTable(Cols, Rows);
//generate the headers
GenerateHeaders(tourID, nightID);
//fill in the scores
GenerateScoreLabels(tourID, nightID, scoreBoard.ShowDivision);
}
Generating the table:
private void GenerateTable(int columnCount, int rowCount)
{
//Clear out the existing controls, we are generating a new table layout
this.tblPnlScoreboard.Controls.Clear();
//Clear out the existing row and column styles
this.tblPnlScoreboard.ColumnStyles.Clear();
this.tblPnlScoreboard.RowStyles.Clear();
//setting up the row and column counts
this.tblPnlScoreboard.ColumnCount = columnCount;
this.tblPnlScoreboard.RowCount = rowCount;
for (int x = 0; x < columnCount; x++)
{
//add a column
if(x==0) //ranking column
{
this.tblPnlScoreboard.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute,30));
}
else if(x==1) //names
{
this.tblPnlScoreboard.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
}
else if(x==columnCount-1) //totals
{
this.tblPnlScoreboard.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
}
else //games
{
this.tblPnlScoreboard.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, (this.tblPnlScoreboard.Width - 130) / columnCount));
}
for (int y = 0; y < rowCount; y++)
{
//add rows. Only do this once, when creating the first column
if (x == 0)
{
if(y==0)
{
this.tblPnlScoreboard.RowStyles.Add(new RowStyle(SizeType.Absolute, 50));
}
else
{
this.tblPnlScoreboard.RowStyles.Add(new RowStyle(SizeType.AutoSize));
}
}
}
}
}
Generating the headers:
private void GenerateHeaders(int TourID, int NightID)
{
//get the players to display
DataTable dtPlayers = players.GetPlayersForTourNightByDivision(tourID, nightID, scoreBoard.ShowDivision);
int Row = 1; //0 is the header row for Games and so on
foreach (DataRow dr in dtPlayers.Rows)
{
//create the label
Label lblPlayer = new Label();
lblPlayer.Name = dr["ID"].ToString(); //name is the ID of the player
lblPlayer.Text = dr["player_name"].ToString(); //the text is the name of the player
lblPlayer.BackColor = Color.Transparent;
lblPlayer.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
lblPlayer.TextAlign = ContentAlignment.MiddleLeft;
lblPlayer.AutoSize = true;
lblPlayer.Height = tblPnlScoreboard.GetRowHeights()[Row];
//add the label to the table
this.tblPnlScoreboard.Controls.Add(lblPlayer, 1, Row);
//create the Total label
Label lblTotal = new Label();
lblTotal.Name = "lbl_total"; //name is arbitrary in this context
lblTotal.Text = dr["Total"].ToString(); //the text is the total
lblTotal.BackColor = Color.Transparent;
lblTotal.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
lblTotal.TextAlign = ContentAlignment.MiddleLeft;
lblTotal.AutoSize = true;
lblTotal.Height = tblPnlScoreboard.GetRowHeights()[Row];
//add the label to the table
this.tblPnlScoreboard.Controls.Add(lblTotal, tblPnlScoreboard.ColumnCount, Row);
//increment the row index
Row++;
}
//totals column
Label lblTotals = new Label();
//lblTotals.Width = this.tblPnlScoreboard.GetColumnWidths()[this.tblPnlScoreboard.ColumnCount - 1];
lblTotals.Height = tblPnlScoreboard.GetRowHeights()[0];
lblTotals.Name = "lbl_total"; //name is the ID of the Game
lblTotals.Text = "Totals"; //text is the display name of the Game
lblTotals.BackColor = Color.Transparent;
lblTotals.ForeColor = Color.White;
lblTotals.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
lblTotals.TextAlign = ContentAlignment.MiddleCenter;
lblTotals.AutoSize = true;
lblTotals.Anchor = (AnchorStyles.None);
//add the label to the table
this.tblPnlScoreboard.Controls.Add(lblTotals, this.tblPnlScoreboard.ColumnCount-1, 0);
//get the games to display
DataTable dtGames = games.GetGamesForTourNightByDivision(tourID, nightID, scoreBoard.ShowDivision);
int Col = 2; //0 is the header column for rank, 1 is the header col for Players
foreach (DataRow dr in dtGames.Rows)
{
//create the label
Label lblGame = new Label();
lblGame.Width = this.tblPnlScoreboard.GetColumnWidths()[Col];
lblGame.Height = tblPnlScoreboard.GetRowHeights()[0];
lblGame.Name = dr["ID"].ToString(); //name is the ID of the Game
lblGame.Text = dr["disp_name"].ToString().Replace("Game ", ""); //text is the display name of the Game
lblGame.BackColor = Color.Transparent;
lblGame.ForeColor = Color.White;
lblGame.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
lblGame.TextAlign = ContentAlignment.MiddleCenter;
lblGame.Anchor = (AnchorStyles.None);
//add the label to the table
this.tblPnlScoreboard.Controls.Add(lblGame, Col, 0);
//increment the column index
Col++;
}
}
Finally, generating the scores:
private void GenerateScoreLabels(int TourID, int NightID, int DivID)
{
//get the id of the playergames record
//expl: each player/game pair has a unique ID - these IDs will be used to update the scores
Players players = new Players();
DataTable dtScores = players.GetPlayerGamesIDsForTourNightByDivision(TourID, NightID, scoreBoard.ShowDivision);
Divisions Divs = new Divisions();
DataTable dtColors = Divs.GetDivisionScoreboardColors(DivID);
foreach (DataRow dr in dtScores.Rows)
{
//find the coordinates in the table, where the textbox should be added
int col = FindX((int)dr["fk_game_id"]);
int row = FindY((int)dr["fk_player_id"]);
if (col > 0 && row > 0)
{
TextBox txt_score = new TextBox();
txt_score.Name = dr["ID"].ToString(); //name of the control is the player/game ID
txt_score.Text = dr["score"].ToString(); //the text in the control is the score
txt_score.ForeColor = Color.Black;
txt_score.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
txt_score.Width = this.tblPnlScoreboard.GetColumnWidths()[col];
txt_score.Height = tblPnlScoreboard.GetRowHeights()[0];
if(row % 2 == 0)
{
txt_score.BackColor = Color.FromArgb((int)dtColors.Rows[0]["sb_even_row_color"]);
}
else
{
txt_score.BackColor = Color.FromArgb((int)dtColors.Rows[0]["sb_odd_row_color"]);
}
txt_score.BorderStyle = BorderStyle.None;
txt_score.TextAlign = HorizontalAlignment.Center;
txt_score.Anchor = (AnchorStyles.Top);
this.tblPnlScoreboard.Controls.Add(txt_score, col, row);
//start the switchboard timer
ttmr_switch.Enabled = true;
}
}
}
On the CellPaint event of the TableLayoutPanel, I have these processes:
private void tblPnlScoreboard_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle r = e.CellBounds;
SolidBrush sb = GetBrushFor(e.Row, e.Column, scoreBoard.ShowDivision);
g.FillRectangle(sb, r);
sb.Dispose();
}
Selection of Colors:
private SolidBrush GetBrushFor(int row, int column, int DivID)
{
DataTable dt_colors = divisions.GetDivisionScoreboardColors(DivID);
if (row == 0)
{ //column headers
SolidBrush brush = new SolidBrush(Color.FromArgb((int)dt_colors.Rows[0]["sb_column_header_color"]));
return brush;
}
else
{
if(row % 2 == 0) //even row
{
SolidBrush brush = new SolidBrush(Color.FromArgb((int)dt_colors.Rows[0]["sb_even_row_color"]));
return brush;
}
else //odd row
{
SolidBrush brush = new SolidBrush(Color.FromArgb((int)dt_colors.Rows[0]["sb_odd_row_color"]));
return brush;
}
}
}
Some people suggested you to use a "proper technology". I would rather say "use properly the technology". Even this weird (sorry) design/implementation choice can be made to work much faster, as shown in the code below, which as you may see is handling rebuilding a table containing 100 rows x 10 columns 10 times per sec - not a big deal compared to professional grids, but far from the original implementation.
Key points:
1. Enclose table rebuild with Suspend/ResumeLayout to avoid intensive recalculations during the process.
2. Use custom double buffered TableLayoutPanel to avoid flickering.
3. Custom paint the data cells to avoid allocating a lot of controls.
Since essential data related parts are missing in the code you gave us, I can't provide you exactly the same working code. Hope you can recognize and map it to your stuff.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ScoreBoardForm { WindowState = FormWindowState.Maximized });
}
}
class ScoreBoardForm : Form
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
players = new List<Player>();
for (int i = 0; i < 100; i++)
players.Add(new Player { ID = i + 1, Name = "P" + (i + 1), Total = random.Next(1000) });
games = new List<Game>();
for (int i = 0; i < 10; i++)
games.Add(new Game { ID = i + 1, Name = "G" + (i + 1) });
scoreBoardTable = new ScoreBoardTable { Dock = DockStyle.Fill, Parent = this };
scoreBoardTable.Bounds = this.DisplayRectangle;
UpdateScoreBoard();
scoreBoardTable.CellPaint += OnScoreBoardTableCellPaint;
updateTimer = new Timer { Interval = 100 };
updateTimer.Tick += (_sender, _e) => UpdateScoreBoard();
updateTimer.Start();
}
private void OnScoreBoardTableCellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
int playerIndex = e.Row - 1, gameIndex = e.Column - 2;
if (playerIndex >= 0 && playerIndex < players.Count && gameIndex >= 0 && gameIndex < games.Count)
{
using (var br = new SolidBrush(GetBackColor(e.Row)))
e.Graphics.FillRectangle(br, e.CellBounds);
var score = GetScore(players[playerIndex], games[gameIndex]);
var sf = new StringFormat { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center };
e.Graphics.DrawString(score.ToString(), defaultCellFont, Brushes.Black, e.CellBounds, sf);
}
}
private int GetScore(Player player, Game game)
{
return random.Next(10000);
}
class ScoreBoardTable : TableLayoutPanel
{
public ScoreBoardTable() { DoubleBuffered = AutoScroll = true; }
}
TableLayoutPanel scoreBoardTable;
Timer updateTimer;
List<Player> players;
List<Game> games;
Random random = new Random();
class Player
{
public int ID;
public string Name;
public int Total;
}
class Game
{
public int ID;
public string Name;
}
private void UpdateScoreBoard()
{
scoreBoardTable.SuspendLayout();
GenerateTable(games.Count + 3, players.Count + 1);
GenerateHeaderCells();
// Custom cell paint is much faster, but requires a good data model.
// If you uncomment the following line, make sure to get rid of CellPaint.
//GenerateScoreCells();
scoreBoardTable.ResumeLayout(true);
}
private void GenerateTable(int columnCount, int rowCount)
{
scoreBoardTable.Controls.Clear();
scoreBoardTable.ColumnStyles.Clear();
scoreBoardTable.RowStyles.Clear();
scoreBoardTable.ColumnCount = columnCount;
scoreBoardTable.RowCount = rowCount;
// Columns
// Ranking
scoreBoardTable.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 30));
// Name
scoreBoardTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
// Games
var percent = (columnCount - 3) / (float)columnCount;
for (int col = 2; col < columnCount - 1; col++)
scoreBoardTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, percent));
// Totals
scoreBoardTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
// Rows
// Header
scoreBoardTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 50));
// Players
for (int row = 1; row < rowCount; row++)
scoreBoardTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
}
private void GenerateHeaderCells()
{
Color backColor = Color.DarkGray, foreColor;
int row, col;
// Header
row = 0;
foreColor = Color.White;
col = 0;
AddCell(row, col++, "rank", "", foreColor, backColor);
AddCell(row, col++, "playerName", "Player", foreColor, backColor);
foreach (var game in games)
AddCell(row, col++, "gameName" + game.ID, game.Name, foreColor, backColor);
AddCell(row, col, "totalColumn", "Totals", foreColor, backColor);
// Rows
foreColor = Color.Black;
row++;
foreach (var player in players)
{
backColor = GetBackColor(row);
AddCell(row, 0, "playerRank_" + player.ID, "", foreColor, backColor, ContentAlignment.MiddleLeft);
AddCell(row, 1, "playerName_" + player.ID, player.Name, foreColor, backColor, ContentAlignment.MiddleLeft);
AddCell(row, scoreBoardTable.ColumnCount, "playerTotal_" + player.ID, player.Total.ToString(), foreColor, backColor, ContentAlignment.MiddleRight);
row++;
}
}
private void GenerateScoreCells()
{
var foreColor = Color.Black;
int row = 1;
foreach (var player in players)
{
var backColor = GetBackColor(row);
int col = 2;
foreach (var game in games)
{
var score = GetScore(player, game);
AddCell(row, col, "score_" + player.ID + "_" + game.ID, score.ToString(), foreColor, backColor, ContentAlignment.MiddleRight, false);
col++;
}
row++;
}
}
static readonly Font defaultCellFont = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
private Label AddCell(int row, int col, string name, string text, Color foreColor, Color backColor, ContentAlignment textAlign = ContentAlignment.MiddleCenter, bool autoSize = true)
{
var label = new Label();
label.Name = name;
label.Text = text;
label.BackColor = backColor;
label.ForeColor = foreColor;
label.Font = defaultCellFont;
label.TextAlign = textAlign;
label.AutoSize = autoSize;
label.Margin = new Padding(0);
label.Dock = DockStyle.Fill;
scoreBoardTable.Controls.Add(label, col, row);
return label;
}
static Color GetBackColor(int row)
{
if (row % 2 == 0) //even row
return Color.Yellow;
else //odd row
return Color.LightGreen;
}
}
}
EDIT And here is equivalent implementation using DataGridView (note that now the number of rows (players) are ten times more at the same refresh rate):
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ScoreBoardForm { WindowState = FormWindowState.Maximized });
}
}
class ScoreBoardForm : Form
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
players = new List<Player>();
for (int i = 0; i < 1000; i++)
players.Add(new Player { ID = i + 1, Name = "P" + (i + 1), Total = random.Next(1000) });
games = new List<Game>();
for (int i = 0; i < 10; i++)
games.Add(new Game { ID = i + 1, Name = "G" + (i + 1) });
InitScoreBoard();
UpdateScoreBoard();
updateTimer = new Timer { Interval = 100 };
updateTimer.Tick += (_sender, _e) => UpdateScoreBoard();
updateTimer.Start();
}
DataGridView scoreBoardTable;
Timer updateTimer;
List<Player> players;
List<Game> games;
Random random = new Random();
class Player
{
public int ID;
public string Name;
public int Total;
}
class Game
{
public int ID;
public string Name;
}
private int GetScore(Player player, Game game)
{
return random.Next(10000);
}
void InitScoreBoard()
{
scoreBoardTable = new DataGridView { Dock = DockStyle.Fill, Parent = this };
scoreBoardTable.Font = new Font("Microsoft Sans Serif", 25, FontStyle.Bold);
scoreBoardTable.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
scoreBoardTable.MultiSelect = false;
scoreBoardTable.CellBorderStyle = DataGridViewCellBorderStyle.None;
scoreBoardTable.BackgroundColor = Color.Honeydew;
scoreBoardTable.ForeColor = Color.Black;
scoreBoardTable.AllowUserToAddRows = scoreBoardTable.AllowUserToDeleteRows = scoreBoardTable.AllowUserToOrderColumns = scoreBoardTable.AllowUserToResizeRows = false;
scoreBoardTable.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
scoreBoardTable.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
scoreBoardTable.RowHeadersVisible = false;
scoreBoardTable.EnableHeadersVisualStyles = false;
var style = scoreBoardTable.DefaultCellStyle;
style.SelectionForeColor = style.SelectionBackColor = Color.Empty;
style = scoreBoardTable.ColumnHeadersDefaultCellStyle;
style.SelectionForeColor = style.SelectionBackColor = Color.Empty;
style.BackColor = Color.Navy;
style.ForeColor = Color.White;
style = scoreBoardTable.RowHeadersDefaultCellStyle;
style.SelectionForeColor = style.SelectionBackColor = Color.Empty;
style = scoreBoardTable.RowsDefaultCellStyle;
style.SelectionForeColor = style.ForeColor = Color.Black;
style.SelectionBackColor = style.BackColor = Color.Yellow;
style = scoreBoardTable.AlternatingRowsDefaultCellStyle;
style.SelectionForeColor = style.ForeColor = Color.Black;
style.SelectionBackColor = style.BackColor = Color.LightGreen;
scoreBoardTable.CellFormatting += OnScoreBoardCellFormatting;
}
private void UpdateScoreBoard()
{
scoreBoardTable.ColumnCount = 3 + games.Count;
for (int c = 0; c < scoreBoardTable.ColumnCount; c++)
{
var col = scoreBoardTable.Columns[c];
if (c == 0)
{
col.Name = "Rank";
col.HeaderText = "";
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
col.Width = 48;
}
else if (c == 1)
{
col.Name = "Player";
col.HeaderText = "Player";
col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
}
else if (c == scoreBoardTable.ColumnCount - 1)
{
col.Name = "Totals";
col.HeaderText = "Totals";
col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
//col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
else
{
var game = games[c - 2];
col.Name = "Game_" + game.ID;
col.HeaderText = game.Name;
col.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
//col.AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
}
}
scoreBoardTable.RowCount = players.Count;
scoreBoardTable.AutoResizeColumnHeadersHeight();
scoreBoardTable.Invalidate();
}
private void OnScoreBoardCellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
var player = players[e.RowIndex];
int col = e.ColumnIndex;
if (col == 0)
e.Value = "";
else if (col == 1)
e.Value = player.Name;
else if (col == scoreBoardTable.ColumnCount - 1)
e.Value = player.Total.ToString();
else
{
var game = games[col - 2];
e.Value = GetScore(player, game).ToString();
}
e.FormattingApplied = true;
}
}
}
Well, the CellPaint event was the culprit. I was able to achieve what I wanted by negating the CellPaint event, and instead manipulating the controls in the panel so as to have the right background colors as well as size so they fill the grid.
Thank you for all your comments - they were very helpful. As per HighCore's comment, I will now be looking into WPF and I might be able to get something even slicker.
It's my problem.
I have a few RadioButtons. If i click on first Radiobutton on form create TextBox, if click on second - create second TextBox and if i click again on first RadioButton, again one TextBox,is it possible?
Give me idea, please.
And without property visible.
There isn't much point in dynamically creating and destroying controls here. It is just a headache, ensuring the position, size and tab order is correct. Just make the text box visible if you like the choice:
private void radioButton2_CheckedChanged(object sender, EventArgs e) {
textBox1.Visible = radioButton2.Checked;
}
Set the textbox' Visible property to False in the designer.
Try something like this (this is only one of them):
TextBox t;
private void radio_CheckedChanged(object sender, System.EventArgs e)
{
if (radio.Checked) {
t = new TextBox();
t.Top = radio.Top;
t.Left = radio.Left + radio.Width;
this.Controls.Add(t);
t.Show();
} else {
if (t!=null)t.Dispose();
}
}
See Only foreach and void TextBoxes.
using System;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
class MyForm : Form
{
private const int
HeightTextBox = 40, WidthTextBox = 25, //размер textboxes
DistanceBetweenTexBoxHeight = 25, DistanceBetweenTexboxWigth = 25; //растояние между ними
private int DimentionalTextBox = 3;
private const int
RadioButtonNumbers = 3, // количество радио кнопок
DistanceBetweenRadiobutton = 50,
RadioButtonFirstGroupStartPositionX = 5,
RadioButtonSecondGroupStartPositionX = 0,
RadioButtonFirstGroupStartPositionY = 0,
RadioButtonSecondGroupStartPositionY = 0,
RadioButtonSize = 25;
public MyForm()
{
//Size of window
ClientSize = new System.Drawing.Size(7 * HeightTextBox + 8 * DistanceBetweenTexBoxHeight,
7 * WidthTextBox + 8 * DistanceBetweenTexboxWigth);
//Create RaioButton
int x = RadioButtonFirstGroupStartPositionX;
int y;
RadioButton[] DimRadioButtons = new RadioButton[RadioButtonNumbers];
for (int i = 0; i < RadioButtonNumbers; i++)
{
DimRadioButtons[i] = new RadioButton();
DimRadioButtons[i].Name = "RadioButton" + (i + 2);
DimRadioButtons[i].Text = Convert.ToString(i + 2);
DimRadioButtons[i].SetBounds(x, RadioButtonFirstGroupStartPositionY, RadioButtonSize, RadioButtonSize);
x += DistanceBetweenRadiobutton;
Controls.Add(DimRadioButtons[i]);
}
//Watch dimention
// And catch even click on RadioButton
foreach (var a in this.Controls)
{
if (a is RadioButton)
{
if (((RadioButton)a).Checked)
{
DimentionalTextBox = Convert.ToInt16(((RadioButton)a).Text);
((RadioButton)a).Click += new EventHandler(this.TextBoxes);
}
}
}
}
// Create-Delete TextBoxes
private void TextBoxes(object sender, EventArgs e)
{
RadioButton rb_click = (RadioButton)sender;
int x = RadioButtonFirstGroupStartPositionX;
int y = 30;
int dim = Convert.ToInt16(rb_click.Text);
TextBox[,] MatrixTextBoxes = new TextBox[dim, dim];
for (int i = 0; i < dim; i++)
{
for (int j = 0; j < dim; j++)
{
MatrixTextBoxes[i, j] = new TextBox();
MatrixTextBoxes[i, j].Top = rb_click.Top;
MatrixTextBoxes[i, j].Name = "MatrixTextBox" + i + j;
MatrixTextBoxes[i, j].Text = i + " " + j;
MatrixTextBoxes[i, j].SetBounds(x, y, WidthTextBox, HeightTextBox);
x += DistanceBetweenTexboxWigth;
this.Controls.Add(MatrixTextBoxes[i, j]);
MatrixTextBoxes[i, j].Show();
}
y += DistanceBetweenTexBoxHeight;
x = RadioButtonFirstGroupStartPositionX;
}
}
}
class MyClassMain : MyForm
{
public static void Main()
{
Application.Run(new MyClassMain());
}
}