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