【问题标题】:Foreach loop to create 100 buttons, painting all buttons at same time as to prevent flickerforeach循环创建100个按钮,同时绘制所有按钮以防止闪烁
【发布时间】:2012-11-02 11:49:14
【问题描述】:

在我的扫雷游戏中,我需要动态创建控件以便在简单 - 中等 - 困难之间切换。假设为了这个问题,hard 包含 100 个按钮。

这就是我创建它们的方式:

this.SuspendLayout(); //Creating so many things that I assume it would be faster to suspend and resume.
foreach (string i in playingField)
{

    Button button = new Button();
    button.Name = i;
    button.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
    button.Margin = new Padding(0);
    button.TabIndex = 0;
    button.Location = new System.Drawing.Point(3, 3);
    button.Size = new System.Drawing.Size(25, 25);
    button.BackgroundImage = blockImage; //##//
    button.MouseDown += new System.Windows.Forms.MouseEventHandler(this.GetUnderSide);
    button.UseVisualStyleBackColor = false;
    minesPanel.Controls.Add(button); //Adding the controls to the container
}
this.ResumeLayout(); //Refer to above. Correct me if I'm wrong please.

如您所见,我正在通过 for 循环创建所有控件,然后添加它们。它导致每个按钮一次被绘制一次。我也试过name.Controls.AddRange(arrayButtons),但仍然导致同样的问题。个人绘画。

我需要的是一种方法来创建所有项目,然后再将它们全部绘制出来,就像用 DoubleBuffered 绘制单个位图一样,但是很遗憾,这也不起作用。

此外,我已经尝试过:

    protected override CreateParams CreateParams
    {
        get
        {
            // Activate double buffering at the form level.  All child controls will be double buffered as well.
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED
            return cp;
        }
    }

哪种有效。它仅适用于应用程序启动。但是,考虑到我将在运行时更改网格大小并添加更多控件,这不是一个可行的选择。

有人告诉我,这就像使用 Graphics 类中的方法一样简单。问题是,我不知道从哪里开始。

如果有人能提供一些帮助,让我的所有控件同时绘制,那就太好了。

【问题讨论】:

  • Windows 窗体? SuspendLayout() 和 ResumeLayout() 可能会有所帮助
  • 我已经在使用SuspendLayoutResumeLayout

标签: c# button controls


【解决方案1】:

遗憾的是,WinForms 不喜欢拥有太多控件,尤其是当您拥有数百个控件时。您永远不能让每个控件同时绘制,因为每个控件都会向窗口发送自己的绘制消息。

我认为使用像 MineSweeper 这样的游戏板的最佳方法是只使用一个控件并绘制按钮网格。

简单地雷类:

public class Mine {
  public Rectangle Bounds { get; set; }
  public bool IsBomb { get; set; }
  public bool IsRevealed { get; set; }
}

然后创建一个从 Panel 控件继承的新控件并设置 DoubleBuffered 属性以防止任何闪烁:

public class MineSweeperControl : Panel {
  private int columns = 16;
  private int rows = 12;
  private Mine[,] mines;

  public MineSweeperControl() {
    this.DoubleBuffered = true;
    this.ResizeRedraw = true;

    // initialize mine field:
    mines = new Mine[columns, rows];
    for (int y = 0; y < rows; ++y) {
      for (int x = 0; x < columns; ++x) {
        mines[x, y] = new Mine();
      }
    }
  }

  // adjust each column and row to fit entire client area:
  protected override void OnResize(EventArgs e) {
    int top = 0;
    for (int y = 0; y < rows; ++y) {
      int left = 0;
      int height = (this.ClientSize.Height - top) / (rows - y);
      for (int x = 0; x < columns; ++x) {
        int width = (this.ClientSize.Width - left) / (columns - x);
        mines[x, y].Bounds = new Rectangle(left, top, width, height);
        left += width;
      }
      top += height;
    }
    base.OnResize(e);
  }

  protected override void OnPaint(PaintEventArgs e) {
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    for (int y = 0; y < rows; ++y) {
      for (int x = 0; x < columns; ++x) {
        if (mines[x, y].IsRevealed) {
          e.Graphics.FillRectangle(Brushes.DarkGray, mines[x, y].Bounds);
        } else {
          ControlPaint.DrawButton(e.Graphics, mines[x, y].Bounds, 
                                  ButtonState.Normal);
        }
      }
    }
    base.OnPaint(e);
  }

  // determine which button the user pressed:
  protected override void OnMouseDown(MouseEventArgs e) {
    for (int y = 0; y < rows; ++y) {
      for (int x = 0; x < columns; ++x) {
        if (mines[x, y].Bounds.Contains(e.Location)) {
          mines[x, y].IsRevealed = true;
          this.Invalidate();
          MessageBox.Show(
            string.Format("You pressed on button ({0}, {1})",
            x.ToString(), y.ToString())
          );
        }
      }
    }
    base.OnMouseDown(e);
  }
}

【讨论】:

  • 这很聪明...我喜欢这个。现在看看我能不能把我的头绕在代码上。
  • 好吧,我做了一个 pastebin 试图理解代码,因为这里发布太长了。 pastebin.com/tUpkM77G 如果你能复习一下并告诉我我有什么错误(术语也是),我将不胜感激,因为我很高兴有机会在可能的时候学习。这是一个很好的答案,所以我将其设置为已接受。
  • 只是一个更新.. 我注意到,当我使用按钮的图像时,按钮的点击会变慢。我就是这样做的:ControlPaint.DrawButton(e.Graphics, mines[x, y].Bounds, ButtonState.Normal);e.Graphics.DrawImage(mineImage, mines[x, y].Bounds); 我认为我需要一种更快的方法来获得按钮点击,而不是遍历数组的每个元素,直到我到达我点击的地方。这也可能是因为图像的格式。如果可以的话,我会尝试 PixelFormat.Format32bppPArgb。能否对点击算法提出一些建议?
  • @Anteara Format32bppPArgb 是 GDI+ 中最快的格式。不过它不应该那么慢,因为我假设它是一个小图像。
  • 感谢您为这个答案付出的努力;它非常符合我的要求。然而,实现这一点需要重写我几乎所有的代码。 (我在一个不同的独立项目上运行我的测试)。然而,由于这是一种做我需要的聪明的方式,当我完成我的游戏时,我可能会编辑它来实现它。从今以后,当计时器完成时,我会给你我的赏金:)。
【解决方案2】:

隐藏minesPanel,绘制按钮,显示minesPanel

【讨论】:

  • 好吧,我在SuspendLayout 之前添加了minesPanel.Hide(),在ResumeLayout 之后添加了minesPanel.Show(),反之亦然,但是这两种组合都没有显示出变化。编辑:如果我隐藏 minesPanel,然后通过单击表单上的按钮完成所有密集工作后手动调用 minesPanel.Show(),它确实加载得更好,但仍然闪烁。
猜你喜欢
  • 1970-01-01
  • 2015-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多