[C#] 在code中新增物件和事件

雖然 Windows Form 很方便要什麼元件,手動拉一拉就好,但還是有機會要從 code 去新增,像下面是作業要做的樂透彩的產生器和對獎器,那麼多個數字要一個個拉動,還要設定 MouseClick 事件,非常辛苦,就想用 for loop 去產生:

大概要注意的點是要讓 Form.Controls.Add() 新增產生出來的物件給 Form,我這裡用的是 Label 和其上的 MouseClick 事件,這裡要用上 delegate,還要在滑鼠按下左鍵時做出背景與前景顏色改變,那就會牽涉到是哪個物件被按,最後是如果需要所在位置 i 的值,來與其他資料作運用,為避免界限例外的一個處理。

namespace TWLotteryExercise
{
  public partial class Form1 : Form
  {
    Label[] lblNumber = new Label[8];
    bool[] isSelected = new bool[8];
    public Form1()
    {
      for (int i = 0; i < 8; i++)
      {
        isSelected[i] = false;        // 用來判斷 Label 是否按下用的陣列
      }
      for (int i = 0; i < 8; i++)
      {
        lblNumber[i] = new Label();            // Label 的設定還有 Location 
        lblNumber[i].Text = $"{i+1}";          // Width Height 等等不贅述
        lblNumber[i].BackColor = Color.White;  // 最初是白底黑字
        lblNumber[i].ForeColor = Color.Black;
        
        int idx = i;        // 若以下事件的方法中用 i 會報錯,另外令一個 idx 就過了
        lblNumber[i].MouseClick += new MouseEventHandler(delegate (object sender, MouseEventArgs e)
        {
          if (e.Button == System.Windows.Forms.MouseButtons.Left)  // 按下的是左鍵
          {
            if(isSelected[idx])     // label 按下之前是已按過
            {
              (sender as Label).BackColor = Color.White;   // 則回到初始白底黑字
              (sender as Label).ForeColor = Color.Black;
              isSelected[idx] = false;    // 記下未按
            }
            else                    // label 按下之前是未按過
            {
              (sender as Label).BackColor = Color.Black;   // 則顯示成黑底白字
              (sender as Label).ForeColor = Color.White;
              isSelected[idx] = true;     // 記下已按
            }
          }
        }
        this.Controls.Add(lblNumSpecial[i]);    // 新增到 Form 的 Controls 裡
      }
    }
  }
}

以上 code 有個很妙的地方,如果在事件裡面的 isSelected[idx] 改用 isSelected[i],會出現 Out of Range Exception 界限例外,偵錯說變數 i 值是 8 ,並不是我按的 Label 的 i,例如我按下寫有「5」的 Label,此物件是 lblNumber[4],理論上他的 i 是 4,那應該會看 isSelected[4] 是 true 還是 false,結果 i 變成 8 了,然後就例外跳出。

如果在事件之前另外令一個 int idx = i; 理論上他和 i 完全一樣,也是區域變數,但他就可以過關,讓對寫有「5」的 Label 按下左鍵時切換顏色。

我猜測會這樣的原因是,使用 i 的寫法在 for loop 跑到最後一個 i = 7 在結束後,步進 i++ 得 i = 8,不符合 for loop 的 condition 而結束,記憶體 i 值是 8 就這樣留著了,然後事件觸發時他又去找這個 i ,然後例外發生。

而先用一個 idx 的作法, 他確實值與 i 相同,但是 for loop 的步進 i++ 並不影響 idx 的值,他會維持在那個物件時的 i ,即使下一個 lblNumber 也是重新建立一個 int idx ,值的記憶體位置已經和上一個 lblNumber 的 idx 不同位址,所以 MouseClick 事件發生時仍然會去找之前的 idx ,指向的確實也是自己的那個 i ,而不是因為步進而改變甚至超過範圍的 i 。

留言