如何在Windows窗体中实例化大量按钮?

时间:2014-07-31 12:32:27

标签: c# .net windows winforms

我正在开发一个影院预订软件。我使用的是Windows Forms,座位由一个二维数组表示。我按下按钮如下:

public void DrawSeats()
{
    // pnl_seats is a Panel
    pnl_seats.Controls.Clear();
    // Here I store all Buttons instance, to later add all buttons in one call (AddRange) to the Panel
    var btns = new List<Control>();
    // Suspend layout to avoid undesired Redraw/Refresh
    this.SuspendLayout();
    for (int y = 0; y < _seatZone.VerticalSize; y++)
    {
        for (int x = 0; x < _seatZone.HorizontalSize; x++)
        {
            // Check if this seat exists
            if (IsException(x, y))
                continue;
            // Construct the button with desired properties. SeatSize is a common value for every button
            var btn = new Button
            {
                Width = SeatSize, 
                Height = SeatSize,
                Left = (x * SeatSize),
                Top = (y * SeatSize),
                Text = y + "" + x,
                Tag = y + ";" + x, // When the button clicks, the purpose of this is to remember which seat this button is.
                Font = new Font(new FontFamily("Microsoft Sans Serif"), 6.5f)
            };

            // Check if it is already reserved
            if (ExistsReservation(x, y))
                btn.Enabled = false;
            else
                btn.Click += btn_seat_Click; // Add click event

            btns.Add(btn);
        }
    }
    // As said before, add all buttons in one call
    pnl_seats.Controls.AddRange(btns.ToArray());
    // Resume the layout
    this.ResumeLayout();
}

但是已经有一个20乘20(400个按钮)的座位区,它花了差不多1分钟来绘制它,并且在调试中我检查了缺乏性能,是按钮的实例化。

有一种方法可以让它更快吗?也许在instatiation期间禁用所有事件或者另一个具有Click事件的轻量级Control?

更新lbl是一项测试,正确的是btn,抱歉。

更新2:

以下是IsExceptionExistsReservations方法:

private bool IsException(int x, int y)
{
    for (var k = 0; k < _seatsExceptions.GetLength(0); k++)
        if (_seatsExceptions[k, 0] == x && _seatsExceptions[k, 1] == y)
            return true;
    return false;
}

private bool ExistsReservation(int x, int y)
{
    for (var k = 0; k < _seatsReservations.GetLength(0); k++)
        if (_seatsReservations[k, 0] == x && _seatsReservations[k, 1] == y)
            return true;
    return false;
}

4 个答案:

答案 0 :(得分:3)

假设您将预订和排除的数组更改为

public List<string> _seatsExceptions = new List<string>();
public List<string> _seatsReservations = new List<string>();

您可以在列表中添加排除和预订,例如

_seatsExceptions.Add("1;10");
_seatsExceptions.Add("4;19");
_seatsReservations.Add("2;5");
_seatsReservations.Add("5;5");

您的排除和预订检查可以更改为

bool IsException(int x, int y)
{
    string key = x.ToString() + ";" + y.ToString();
    return _seatsExceptions.Contains(key);
}
bool ExistsReservation(int x, int y)
{
    string key = x.ToString() + ";" + y.ToString();
    return _seatsReservations.Contains(key);
}

当然,我不知道您是否能够在您的计划中进行此更改。但请考虑迟早更改阵列上的搜索。

编辑我做了一些测试,虽然20x20按钮的虚拟网格工作得很好(平均31毫秒0.775毫秒),但更大的按钮明显减慢。在200x50时,时间跳跃到10秒(平均为1,0675)。所以也许需要一种不同的方法。绑定的DataGridView可能是一个更简单的解决方案,并且相对容易处理。

答案 1 :(得分:3)

我也不会使用这么多无数的控件来实现这样的功能。相反,您应该创建自己的UserControl,它将所有座位绘制为图像并对点击事件做出反应。

为了让你更容易一点,我创建了一个简单的UserControl,它将绘制所有座位并在鼠标单击时做出反应以更改状态。这是:

public enum SeatState
{
    Empty,
    Selected,
    Full
}

public partial class Seats : UserControl
{
    private int _Columns;
    private int _Rows;
    private List<List<SeatState>> _SeatStates;

    public Seats()
    {
        InitializeComponent();
        this.DoubleBuffered = true;

        _SeatStates = new List<List<SeatState>>();
        _Rows = 40;
        _Columns = 40;
        ReDimSeatStates();

        MouseUp += OnMouseUp;
        Paint += OnPaint;
        Resize += OnResize;
    }

    public int Columns
    {
        get { return _Columns; }
        set
        {
            _Columns = Math.Min(1, value);
            ReDimSeatStates();
        }
    }

    public int Rows
    {
        get { return _Rows; }
        set
        {
            _Rows = Math.Min(1, value);
            ReDimSeatStates();
        }
    }

    private Image GetPictureForSeat(int row, int column)
    {
        var seatState = _SeatStates[row][column];

        switch (seatState)
        {
            case SeatState.Empty:
                return Properties.Resources.emptySeat;

            case SeatState.Selected:
                return Properties.Resources.choosenSeat;

            default:
            case SeatState.Full:
                return Properties.Resources.fullSeat;
        }
    }

    private void OnMouseUp(object sender, MouseEventArgs e)
    {
        var heightPerSeat = Height / (float)Rows;
        var widthPerSeat = Width / (float)Columns;

        var row = (int)(e.X / widthPerSeat);
        var column = (int)(e.Y / heightPerSeat);
        var seatState = _SeatStates[row][column];

        switch (seatState)
        {
            case SeatState.Empty:
                _SeatStates[row][column] = SeatState.Selected;
                break;

            case SeatState.Selected:
                _SeatStates[row][column] = SeatState.Empty;
                break;
        }

        Invalidate();
    }

    private void OnPaint(object sender, PaintEventArgs e)
    {
        var heightPerSeat = Height / (float)Rows;
        var widthPerSeat = Width / (float)Columns;

        e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
        e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

        for (int row = 0; row < Rows; row++)
        {
            for (int column = 0; column < Columns; column++)
            {
                var seatImage = GetPictureForSeat(row, column);
                e.Graphics.DrawImage(seatImage, row * widthPerSeat, column * heightPerSeat, widthPerSeat, heightPerSeat);
            }
        }
    }

    private void OnResize(object sender, System.EventArgs e)
    {
        Invalidate();
    }

    private void ReDimSeatStates()
    {
        while (_SeatStates.Count < Rows)
            _SeatStates.Add(new List<SeatState>());

        if (_SeatStates.First().Count < Columns)
            foreach (var columnList in _SeatStates)
                while (columnList.Count < Columns)
                    columnList.Add(SeatState.Empty);

        while (_SeatStates.Count > Rows)
            _SeatStates.RemoveAt(_SeatStates.Count - 1);

        if (_SeatStates.First().Count > Columns)
            foreach (var columnList in _SeatStates)
                while (columnList.Count > Columns)
                    columnList.RemoveAt(columnList.Count - 1);
    }
}

目前这将绘制40行和列(因此有800个座位),您可以点击每个座位来更改其状态。

以下是我使用的图片:

  • EmtpySeat:emptySeat
  • ChoosenSeat:choosenSeat
  • FullSeat:fullSeat

如果您锚定此控件并调整其大小,或者您单击一个座位以更改其状态,如果您进一步增加行数或列数,可能会有一些轻微的重新绘制,但这仍然远低于一秒。如果这仍然伤害你,你必须改进paint方法,也许检查paint事件的ClipRectangle属性,只绘制真正需要的部分,但这是另一个故事。

答案 2 :(得分:1)

不是使用实际的按钮控件,只需绘制座位的图像,然后当用户点击座位时,翻译鼠标X,Y坐标以确定单击了哪个座位。这样会更有效率。当然,缺点是您必须编写将x,y坐标转换为座位的方法,但这确实不是那么困难。

答案 3 :(得分:0)

修改;有人指出,这在Windows窗体中不起作用!

嗯,你正在顺序完成它。 如果一次迭代花费1秒,则整个过程将花费400 * 1的时间。

也许您应该尝试制作对象的集合,并对其进行并行处理。

尝试.Net框架(4及以上版本)&#39;并行foreach&#39;方法:  http://msdn.microsoft.com/en-s/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx

编辑:所以,如果你有一个列表按钮名,你可以说

buttonNames.ForEach(x=>CreateButton(x));

,而您的CreateButton()方法如下:

  

private Button CreateButton(string nameOfButton){Button b = new   按钮(); b.Text = nameOfButton; //做你想做的...   返回b; }