使用TableLayoutPanel渲染生成的表需要很长时间才能完成

时间:2015-08-18 12:51:15

标签: c# winforms

我的计划在概念上非常直接 - 它允许用户在保龄球锦标赛期间获得分数,以及通过记分牌向玩家展示分数。

有一个分数表单,用于输入分数,以及一个记分牌表单,按分区显示给玩家的分数。记分板在与主程序不同的线程中运行。

记分板由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;
            }
        }
    }

2 个答案:

答案 0 :(得分:0)

嗯,CellPaint事件是罪魁祸首。我能够通过否定CellPaint事件来实现我想要的,而是操纵面板中的控件,以便具有正确的背景颜色和大小,以便它们填充网格。

感谢您的所有评论 - 他们非常乐于助人。根据HighCore的评论,我现在将研究WPF,我可能会得到一些更加光滑的东西。

答案 1 :(得分:0)

有些人建议你使用“适当的技术”。我宁愿说“正确使用技术”。即使是这种奇怪的(对不起)设计/实现选择也可以更快地工作,如下面的代码所示,正如您所看到的那样,处理重建包含100行×10列每秒10次的表 - 这不是什么大不了的事与专业网格相比,但远非原始实施。 要点:
1.使用Suspend/ResumeLayout封闭表重建,以避免在此过程中进行大量重新计算 2.使用自定义双缓冲TableLayoutPanel以避免闪烁 3.自定义绘制数据单元格以避免分配大量控件。

由于您提供给我们的代码中缺少必要的数据相关部分,因此我无法为您提供完全相同的工作代码。希望你能识别并将其映射到你的东西。

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

编辑以下是使用DataGridView的等效实现(请注意,现在在相同的刷新率下行数(播放器)的数量是十倍):

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