【问题标题】:how can I split a panel to clickable segments in c# winform?如何在 c# winform 中将面板拆分为可点击的段?
【发布时间】:2018-08-20 17:42:56
【问题描述】:

我正在尝试用 c# 模拟 LED 显示板。我需要一个control,其中包含 1536 个可点击的controls 来模拟 LED(宽度为 96,高度为 16)。我为此使用了一个名为pnlContainerpanel,用户将在运行时添加1536 个微小的自定义panels。这些定制的panels 应该在运行时通过点击事件改变它们的颜色。一切正常。但是将这个数量的微小panels 添加到容器中需要很长时间(大约 10 秒)。你对解决这个问题有什么建议?任何提示都表示赞赏。

这是我的客户panel:

public partial class LedPanel : Panel
{
    public LedPanel()
    {
        InitializeComponent();
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);

    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            if (this.BackColor == Color.Black)
            {
                this.BackColor = Color.Red;
            }
            else
            {
                this.BackColor = Color.Black;
            }
        }
    }
}

这是一段将微小的panels 添加到pnlContainer 的代码:

private void getPixels(Bitmap img2)
    {

        pnlContainer.Controls.Clear();

        for (int i = 0; i < 96; i++)
        {
            for (int j = 0; j < 16; j++)
            {

                Custom_Controls.LedPanel led = new Custom_Controls.LedPanel();
                led.Name = i.ToString() + j.ToString();
                int lWidth = (int)(pnlContainer.Width / 96);
                led.Left = i * lWidth;
                led.Top = j * lWidth;
                led.Width = led.Height = lWidth;
                if (img2.GetPixel(i, j).R>numClear.Value)
                {
                    led.BackColor = Color.Red;
                }
                else
                {
                    led.BackColor = Color.Black;
                }
                led.BorderStyle = BorderStyle.FixedSingle;


                pnlContainer.Controls.Add(led);

            }
        }

    }

有没有更好的方法或更好的control 而不是panel来做到这一点?

【问题讨论】:

  • 是的。任何事情都比向 Winforms 应用程序添加 1k+ 控件更好。我建议您在代码中完成所有操作,即对 Paint 事件和 MouseClcik 进行编码以完成所有需要的工作。还要创建一个数据结构,用于查找点击的位置以及在哪里绘制什么状态。也许是具有颜色或状态以及矩形或位置的类或元组.. - 另请注意,GetPixel 是一项缓慢的操作,因为它所做的比人们希望的要多得多。 Acceissng 你的数据结构会快很多,但是从图像加载总是需要一些时间,除非你使用 lockbit,这为时过早..
  • 这个我也想过,但问题是我最后还是想提供一个黑点和红点的数组。如果我使用Paint,如何找到红点的数量和位置
  • 你需要将数据存储在一些数据结构的二维数组中,也许只是布尔值。然后,您可以计算从索引中绘制什么的位置。附加优势:你很灵活 wrt 'led(pixel'-size..
  • 如果您只想优化初始负载,也许您可​​以将面板添加到列表而不是控件集合中。当所有人都在那里时,您可以执行 panel.Controls.AddRange(theList.ToArray()),这将比 1k+ 单个 Adds 快很多..
  • 类似这样的东西:Grid I can paint on? (VB.Net,但它是用 C# 编写的)在该示例中,没有控件,但每个单元格都可以在事件上发生。

标签: c# winforms performance runtime panel


【解决方案1】:

我同意@TaW 的建议。不要在表单上放置 1000 多个控件。使用某种数据结构,如数组来跟踪哪些 LED 需要点亮,然后在 Panel 的 Paint 事件中绘制它们。

这是一个例子。在表单上放置一个面板并将其命名为ledPanel。然后使用类似于以下的代码。我只是随机设置布尔数组的值。您需要适当地设置它们以响应单击鼠标。我没有包含该代码,但基本上您需要获取鼠标单击的位置,确定需要设置(或取消设置)哪个数组条目,然后使面板无效,以便重新绘制自身。

public partial class Form1 : Form
{
    //set these variables appropriately
    int matrixWidth = 96;
    int matrixHeight = 16;

    //An array to hold which LEDs must be lit
    bool[,] ledMatrix = null;

    //Used to randomly populate the LED array
    Random rnd = new Random();

    public Form1()
    {
        InitializeComponent();

        ledPanel.BackColor = Color.Black;

        ledPanel.Resize += LedPanel_Resize;

        //clear the array by initializing a new one
        ledMatrix = new bool[matrixWidth, matrixHeight];

        //Force the panel to repaint itself
        ledPanel.Invalidate();
    }

    private void LedPanel_Resize(object sender, EventArgs e)
    {
        //If the panel resizes, then repaint.  
        ledPanel.Invalidate();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        //clear the array by initializing a new one
        ledMatrix = new bool[matrixWidth, matrixHeight];

        //Randomly set 250 of the 'LEDs';
        for (int i = 0; i < 250; i++)
        {
            ledMatrix[rnd.Next(0, matrixWidth), rnd.Next(0, matrixHeight)] = true;
        }

        //Make the panel repaint itself
        ledPanel.Invalidate();
    }

    private void ledPanel_Paint(object sender, PaintEventArgs e)
    {
        //Calculate the width and height of each LED based on the panel width
        //and height and allowing for a line between each LED
        int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
        int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);

        //Loop through the boolean array and draw a filled rectangle
        //for each one that is set to true
        for (int i = 0; i < matrixWidth; i++)
        {
            for (int j = 0; j < matrixHeight; j++)
            {
                if (ledMatrix != null)
                {
                    //I created a custom brush here for the 'off' LEDs because none
                    //of the built in colors were dark enough for me. I created it
                    //in a using block because custom brushes need to be disposed.
                    using (var b = new SolidBrush(Color.FromArgb(64, 0, 0)))
                    {
                        //Determine which brush to use depending on if the LED is lit
                        Brush ledBrush = ledMatrix[i, j] ? Brushes.Red : b;

                        //Calculate the top left corner of the rectangle to draw
                        var x = (i * (cellWidth + 1)) + 1;
                        var y = (j * (cellHeight + 1) + 1);

                        //Draw a filled rectangle
                        e.Graphics.FillRectangle(ledBrush, x, y, cellWidth, cellHeight);
                    }
                }
            }
        }
    }

    private void ledPanel_MouseUp(object sender, MouseEventArgs e)
    {
        //Get the cell width and height
        int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
        int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);

        //Calculate which LED needs to be turned on or off
        int x = e.Location.X / (cellWidth + 1);
        int y = e.Location.Y / (cellHeight + 1);

        //Toggle that LED.  If it's off, then turn it on and if it's on, 
        //turn it off
        ledMatrix[x, y] = !ledMatrix[x, y];

        //Force the panel to update itself.
        ledPanel.Invalidate();
    }
}

我相信这段代码可以有很多改进,但它应该让你知道如何去做。

【讨论】:

  • 请在构造函数中包含DoubleBuffered = true;,这样它就不会闪烁了!
  • @Chris Dunaway。谢谢你 。但是我如何检测点击了哪个rectangle。每个rectangle 都应该通过单击从红色变为黑色或黑色变为红色来更改其颜色,并且数组中的相应值应更改
  • @mohammadboluki - 为此,您必须处理面板的 MouseUp 事件并使用 MouseEventArgs.Location 属性来获取鼠标点击的坐标。使用这些值计算布尔数组中的位置并切换该值。我添加了代码。
  • @TaW - 在表单中添加 DoubleBuffered = true 无法解决闪烁问题。我必须创建一个名为LedPanel 的新类,它派生自Panel。我在该构造函数中设置了DoubleBuffered = true,这似乎解决了闪烁问题。
  • 确实如此。我以为您已经在使用课程,但这是另一个答案;我的错。对于那个很抱歉。您还可以使用函数在任何控件上启用 DoubleBuffering。见here
【解决方案2】:

@Chris 和@user10112654 是对的。 这是一个类似于@Chris 的代码,但将显示逻辑隔离在一个单独的类中。 (@Chris 在我编写代码时回答了您的问题 :))))
只需创建一个二维数组来初始化类并将其传递给 Initialize 方法。

    public class LedDisplayer
    {
        public LedDisplayer(Control control)
        {
            _control = control;
            _control.MouseDown += MouseDown;
            _control.Paint += Control_Paint;

            // width and height of your tiny boxes
            _width = 5;
            _height = 5;

            // margin between tiny boxes
            _margin = 1;
        }

        private readonly Control _control;
        private readonly int _width;
        private readonly int _height;
        private readonly int _margin;
        private bool[,] _values;

        // call this method first of all to initialize the Displayer
        public void Initialize(bool[,] values)
        {
            _values = values;
            _control.Invalidate();
        }

        private void MouseDown(object sender, MouseEventArgs e)
        {
            var firstIndex = e.X / OuterWidth();
            var secondIndex = e.Y / OuterHeight();
            _values[firstIndex, secondIndex] = !_values[firstIndex, secondIndex];
            _control.Invalidate(); // you can use other overloads of Invalidate method for the blink problem
        }

        private void Control_Paint(object sender, PaintEventArgs e)
        {
            if (_values == null)
                return;

            e.Graphics.Clear(_control.BackColor);
            for (int i = 0; i < _values.GetLength(0); i++)
                for (int j = 0; j < _values.GetLength(1); j++)
                    Rectangle(i, j).Paint(e.Graphics);
        }

        private RectangleInfo Rectangle(int firstIndex, int secondIndex)
        {
            var x = firstIndex * OuterWidth();
            var y = secondIndex * OuterHeight();
            var rectangle = new Rectangle(x, y, _width, _height);

            if (_values[firstIndex, secondIndex])
                return new RectangleInfo(rectangle, Brushes.Red);

            return new RectangleInfo(rectangle, Brushes.Black);
        }

        private int OuterWidth()
        {
            return _width + _margin;
        }

        private int OuterHeight()
        {
            return _height + _margin;
        }
    }

    public class RectangleInfo
    {
        public RectangleInfo(Rectangle rectangle, Brush brush)
        {
            Rectangle = rectangle;
            Brush = brush;
        }

        public Rectangle Rectangle { get; }
        public Brush Brush { get; }

        public void Paint(Graphics graphics)
        {
            graphics.FillRectangle(Brush, Rectangle);
        }
    }

这是它在表单中的使用方式:

    private void button2_Click(object sender, EventArgs e)
    {
        // define the displayer class 
        var displayer = new LedDisplayer(panel1);

        // define the array to initilize the displayer
        var display = new bool[,]
        {
            {true, false, false, true },
            {false, true, false, false },
            {false, false, true, false },
            {true, false, false, false }
        };

        // and finally
        displayer.Initialize(display);
    }

【讨论】:

  • 好,但有基本错误。 Winforms 图形基本规则#1:永远不要使用control.CreateGraphics!永远不要尝试缓存 Graphics 对象!使用Graphics g = Graphics.FromImage(bmp) 或在控件的Paint 事件中使用e.Graphics 参数绘制到Bitmap bmp
猜你喜欢
  • 2012-10-19
  • 1970-01-01
  • 2016-09-08
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多