【发布时间】:2015-08-20 03:10:03
【问题描述】:
我的程序在概念上非常简单 - 它允许用户在保龄球比赛期间记分,并通过记分牌向玩家显示分数。
有一个计分表表格,他们可以在其中输入分数,还有一个计分板表格,可以按部门向玩家显示分数。记分牌在与主程序不同的线程中运行。
记分板由一个 TableLayoutPanel 组成,我以编程方式对其进行操作以表示要显示的分数表。我的问题是表格渲染需要很长时间(尤其是对于一长串玩家)。观看表格呈现本身的用户体验也不尽如人意。
我测试了渲染文本框、标签和图片框的速度以减轻负载;文本框赢了,所以我把分数标签改成了文本框……但还是不够。
要查看的内容很多,但如果有人知道如何进一步加快记分牌的渲染速度,我会全神贯注。
这是我的流程分解。
调用方法(定时器每15秒调用一次):
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);
}
生成表格:
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));
}
}
}
}
}
生成标题:
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++;
}
}
最后,生成分数:
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;
}
}
}
在TableLayoutPanel的CellPaint事件上,我有这些流程:
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();
}
颜色选择:
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;
}
}
}
【问题讨论】:
-
那里有很多跨线程调用(取决于使用的 Timer),更新 UI 元素应该只发生在 UI 线程中。当您完成 GDI 资源时,您也不会对其进行处置。
-
改用 DataGridView 怎么样?您可以使用 DataTable 作为 DataSource,您只需配置一些 TextBox 列。
-
定时器是一个system.Timer...你说的GDI资源是什么意思?我对此很陌生。
-
是的,如果您使用的是 System.Threading.Timer,那么您有一个跨线程调用问题。 GDI资源是SolidBrush,用完后调用它的
.Dispose方法。这需要中断您对GetBrushFor的调用并将其存储在一个临时变量中,然后在调用FillRectangle之后将其处理掉。 -
@taffer 我也想过使用 DataGridView ......但它没有让我获得以客户想要的方式显示表格所需的灵活性。