【.NET】ボタンクリックで入力フォーム部品を規則的に増やす

# 環境

- Visual Studio 2019
- Windows Forms App(.NET Core)
- Visual C#

# はじめに
特定のボタンをクリックすると、**入力フォームの部品を規則的に増やす方法**について説明します。
ここでは下図のようなフォームで「プログラムの追加」ボタンをクリックすると、ラベル・テキストボックス・参照ボタンの一式を規則的に下に増やしたいという想定で話を進めます。
<img width="400" alt="1_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598279/c744a242-9036-e9eb-abd0-d9c6c60f3284.png">
# Form.cs[Design]の設定
まずはフォームのデザイン側の設定をしていきます。
デザイン側での設定は

- 増加させる部品の親要素（コンテナやフォームウィンドウ）のスクロール設定
- 要素（コンポーネント）の名前付け

の2点を行います。 
部品を増やしていくと親要素の表示範囲を超えていくことが予想されますので、**親要素にはスクロールできるように設定**します。
私の場合、Panelというコンテナの中にこの一式を配置しているので、このPanelをスクロールできるようにします。
下のように**親要素（コンポーネント）のプロパティ**の設定を変更して下さい。

|コンポーネント|プロパティ|値|
|:--|:--|:--|
|増加させたい部品の親要素（コンテナまたはフォームウィンドウ）|AutoScroll|True|

**Name**は自分が分かりやすい識別子をつけてください。
ここでは下のようにNameを設定しています。

||コンポーネント|Name|
|:--|:--|:--|
|1|「プログラムを追加」ボタン|addProgButton|
|2|「プログラム名」ラベル|prognameLabel1|
|3|「パス」ラベル|pathLabel1|
|4|「プログラム名」テキストボックス|prognameTextBox1|
|5|「パス」テキストボックス|pathTextBox1|
|6|「参照」ボタン|progRefeButton1|
|7|親要素のパネル|allprogPanel|

<img width="400" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598279/88465ed5-50ad-6b17-49fe-55e61bb39ed8.png">
# Form.csのコーディング
Form.csのコードは次のようになります。
**上のName表と対応しているので合わせて見てください。**

クリックイベントの処理の流れは以下の通りです。
クリック回数のカウント
↓
コンポーネント一式の情報を設定
（Name属性、表示するテキスト、表示位置、既存コンポーネントと新規コンポーネントの距離）
↓
設定した情報からコンポーネントを生成する

```cs:Form.cs
//「プログラムを追加」ボタンのクリックイベント
private void addProgButton_Click(object sender, EventArgs e)
  {
    numberofAPBClicks++; //クリック回数のカウント、同クラスのメンバ変数

    //prognameLabel
    string PNLABEL_NAME = $"prognameLabel{numberofAPBClicks+1}";
    string PNLABEL_TEXT = $"{numberofAPBClicks+1}. プログラム名";
    //既存ラベルと新規ラベルの距離
    const int PNLABEL_OFFSET = 100; 
    int[] PNLABEL_LOCATE = {
      prognameLabel1.Location.X, //X座標
      PNLABEL_OFFSET * numberofAPBClicks + allProgPanel.AutoScrollPosition.Y //Y座標
    };

    //pathLabel
    string PATHLABEL_NAME = $"pathLabel{numberofAPBClicks+1}";
    const string PATHLABEL_TEXT = "パス";
    const int PATHLABEL_OFFSET = 100;
    int[] PATHLABEL_LOCATE = {
      pathLabel1.Location.X, //X座標
      PATHLABEL_OFFSET * numberofAPBClicks + allProgPanel.AutoScrollPosition.Y //Y座標
    };

    //prognameTextBox
    string PNBOX_NAME = $"prognameTextBox{numberofAPBClicks+1}";
    const int PNBOX_OFFSET = 100; //テキストボックス間の距離（Y）
    const int PNBOX_FIRST_OFFSET = 28; //Panelと最上部のテキストボックスとの距離（Y）
    int[] PNBOX_LOCATE = { 
      prognameTextBox1.Location.X, //X座標
      PNBOX_OFFSET * numberofAPBClicks + PNBOX_FIRST_OFFSET + allProgPanel.AutoScrollPosition.Y //Y座標
    };
    int[] PNBOX_SIZE = {
      prognameTextBox1.Size.Width, //テキストボックス幅
      prognameTextBox1.Size.Height //テキストボックス高さ
    };

    //pathTextBox
    string PATHBOX_NAME = $"pathTextBox{numberofAPBClicks+1}";
    const int PATHBOX_OFFSET = 100;
    const int PATHBOX_FIRST_OFFSET = 28; //Panelと最上部のテキストボックスとの距離（Y）
    int[] PATHBOX_LOCATE = {
      pathTextBox1.Location.X, //X座標
      PATHBOX_OFFSET * numberofAPBClicks + PATHBOX_FIRST_OFFSET + allProgPanel.AutoScrollPosition.Y //Y座標
    };
    int[] PATHBOX_SIZE = {
      pathTextBox1.Size.Width, //テキストボックス幅
      pathTextBox1.Size.Height //テキストボックス高さ
    };

    //progRefeButton
    string PRBUTTON_NAME = $"progRefeButton{numberofAPBClicks+1}";
    const string PRBUTTON_TEXT = "参照";
    int PRBUTTON_TAG = numberofAPBClicks + 1;
    const int PRBUTTON_OFFSET = 100;
    const int PRBUTTON_FIRST_OFFSET = 28; 
    int[] PRBUTTON_LOCATE ={
      progRefeButton1.Location.X,
      PRBUTTON_OFFSET * numberofAPBClicks + PRBUTTON_FIRST_OFFSET + allProgPanel.AutoScrollPosition.Y
    };
    int[] PRBUTTON_SIZE ={
    progRefeButton1.Size.Width,
    progRefeButton1.Size.Height
    };

  createLabel(PNLABEL_NAME, PNLABEL_TEXT, PNLABEL_LOCATE);
  createLabel(PATHLABEL_NAME, PATHLABEL_TEXT, PATHLABEL_LOCATE);
  createTextBox(PNBOX_NAME, PNBOX_LOCATE, PNBOX_SIZE);
  createTextBox(PATHBOX_NAME, PATHBOX_LOCATE, PATHBOX_SIZE);
  createButton(PRBUTTON_NAME, PRBUTTON_TEXT, PRBUTTON_TAG, PRBUTTON_LOCATE, PRBUTTON_SIZE);
}


//ラベルを生成するメソッド
//引数にはName属性、表示されるテキスト、ラベルの表示位置を渡す
private void createLabel(string name, string text, int[] locate)
{
  //ラベルのインスタンス生成
  Label label = new Label();
  //プロパティ設定
  label.Name = name;
  label.Location = new Point(locate[0], locate[1]);
  label.Text = text;
  //ラベルをパネルの子要素として追加
  allProgPanel.Controls.Add(label);
}

//テキストボックスを生成するメソッド
//引数にはName属性、ボックスの表示位置、ボックスの大きさを渡す
private void createTextBox(string name, int[] locate, int[] size)
{
  TextBox textBox = new TextBox();
  textBox.Name = name;
  textBox.Location = new Point(locate[0], locate[1]);
  textBox.Size = new Size(size[0], size[1]);
  allProgPanel.Controls.Add(textBox);
  return textBox;
}

//ボタンを生成するメソッド
//引数にはName属性、ボタンに表示されるテキスト、ボタンの表示位置、ボタンの大きさを渡す
private void createButton(string name, string text, int[] locate, int[] size)
{
  Button button = new Button();
  button.Name = name;
  button.Text = text;
  button.Location = new Point(locate[0], locate[1]);
  button.Size = new Size(size[0], size[1]);
  button.Click += progRefeButton_Click;
  allProgPanel.Controls.Add(button);
}
```
# 補足
## 要素（コンポーネント）のName属性とは
要素の**Name属性**について説明しておきます。
<img width="243" alt="3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598279/f8352c03-9c05-9ce1-fafb-b02d24a9f9c4.png">
Name属性を指定することでコーディングをする際に、その指定したName値で**要素のプロパティにアクセス**したり、**各種イベント（クリックイベントなど）を検知**したりできます。
なおデフォルトでは、ラベルの場合はlabel1、テキストボックスの場合はtextBox1などと設定されています。

例えば、`label1.text = "Hello World!";`でプログラム上でラベルのテキストを変更できますし、`label1.BackColor = Black`などとすることでラベルの背景色を変更できます。
アクセスできるプロパティや呼び出すことができるメソッド、設定できるイベントなどの詳細は<a href="https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.label?view=netcore-3.1" target=_blank>こちら（Labelの場合）</a>を確認してください