【C#】ソースで動的に追加したコントロール - 1.動的に追加したボタンにクリックイベントを付与する

# コントロールを動的にフォームへ追加して使いたい

## C#の記事
- [C#でMySQLを使う - 1.開発環境インストール](https://qiita.com/kazumi75kitty/items/9953bb84edd7717bc90a)
- [C#でMySQLを使う - 2.SELECT・画面表示](https://qiita.com/kazumi75kitty/items/dd391cab06762555dc5d)
- [C#でMySQLを使う - 3.追加更新と削除](https://qiita.com/kazumi75kitty/items/65f8ae261bbe61964627)
- [C#でのデータベースのデータのNULLかどうかの判定](https://qiita.com/kazumi75kitty/items/64002467350a1ea08ab0)
- [C#のdelegate(デリゲート)と=>(ラムダ式)はC言語の関数へのポインタに似た扱い](https://qiita.com/kazumi75kitty/items/fdf4e1d2ea8e9c9326ba#comment-dbed4d53e43dbfdf5baa)
- 動的にコントロールを追加 - 1.ボタンクリック【この記事】
- [動的にコントロールを追加 - 2.テキストボックスの参照](https://qiita.com/kazumi75kitty/items/093b1d679e016782d3af)
- [動的にコントロールを追加 - 3.DB(MySQL)から動的に作成](https://qiita.com/kazumi75kitty/items/3bbb8afdfbc21fb6b3f1)

通常は、ボタンのクリックや、テキストボックスを参照するときは、

1. あらかじめフォームにボタンやテキストボックスを貼り付ける
2. あらかじめ張り付けられたボタンにクリックイベントメソッドを定義する
3. 何らかのイベントでテキストボックスを参照する際は、あらかじめ張り付けられたテキストボックスのNameを参照する

ことがほとんどですが、時には**マスタデータや設定ファイルの内容に応じて、動的にコントロールを柔軟に設置したい**こともあるし、例えばユーザーとなるお客さんに応じて、ある業界共通のプログラムを使う際に、必要な機能とそうでない機能が出てくるとは思います。けど**プログラムをカスタマイズするということは、カスタマイズに必要な工数が発生する**ということで、大企業に限ればカスタマイズは最小限でしょうけど、りらぽ♥︎は全国的大企業だけにC#の恩恵を受ける前提でいる思想は全くなく、**地元大企業や中小企業・個人商店にもC#の恩恵を受けるためにも、数多くのお客さん向けにC#プログラムを提供できることを前提としてQiitaを使っているわけ**なので、そうなれば当然、相手する顧客数は圧倒的に増えます。そうなってくると、ものすごい数に対するお客さんに毎回プログラムをカスタマイズとなると、日が暮れてしまいます。。。(⑉•̆ ·̭ •⑉)

それだからこそ、マスタファイルや設定ファイルなどで、C#のソースコードをいじることなく動的にコントロールを追加して、それを使えないか？？というのを、まずはどのように実装したらいいのかのレベルで調べてみたんです(˶ ･ᴗ･ )੭⚐⚑

今回はデータベースや設定ファイルはまだ使いませんが、実装すべき基盤をどう記述すればいいのかだけは載せておきます

## どうやって動的に実装する？？

- C#ソースにコントロールのオブジェクトのインスタンスを作成
- コントロールのインスタンスにプロパティをいろいろセットする
- フォームにそのインスタンスを追加して、画面に表示される
- これだけでは使い物にならないので、何らかのイベントを付与するか、識別できるようにして参照可能な状態にする必要がある

という方法で、フォームデザインを使わずに、ソースコードを記述して動的にコントロールを追加することができます。ただ単に追加するという意味ではフォームデザイン使った方が手っ取り早いですが。。。

# ボタンを動的にコントロール追加してみる
## まずはボタンのソースコードからの動的追加のみから始める
動的にコントロールを追加しようと思うなら、単にコントロールを追加しただけでは当然使い物はならないので、クリックイベントといった動作も動的に追加する必要があるものの…だからといってソースコードではまた手動でメソッドを指定してあげなければならないので、いっぺんに試そうとしても混乱してくるので、**最初はコントロールをどのようにしてソースから動的追加していくかを**試す

![フォームデザイン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617906/37a8d7b6-031d-53a4-b199-72c367e62dce.png)
とりあえずフォームにボタン「*formCallButton*」というボタンだけを設置して、追加したボタンにクリックイベントを設定し、そのクリックイベント処理の中に、フォーム左に動的にボタンを追加する、ということをやってみました。

```c#:Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyTrainingCsFrm1
{
    public partial class Form1 : Form
    {
        // フォーム呼び出しで作成するボタン
        private Button[] manyButtons;

        public Form1()
        {
            InitializeComponent();
            this.manyButtons = null;
        }

        // フォームを呼び出すボタン
        private void formCallButton_Click(object sender, EventArgs e)
        {
            if (this.manyButtons != null)
            {
                MessageBox.Show("フォームはすでに表示されています");
                return;
            }
            
            // ボタンのインスタンス作成(5つ)
            this.manyButtons = new Button[5];
            for (int i = 0; i < this.manyButtons.Length; i++)
            {
                this.manyButtons[i] = new Button();

                // コントロールのプロパティ
                this.manyButtons[i].Name = "OriginalButton" + i;
                this.manyButtons[i].Text = "ボタン" + i;
                this.manyButtons[i].Location = new Point(10, 10 + i * 22);
                this.manyButtons[i].Size = new Size(80, 20);

                // フォームへの追加
                this.Controls.Add(this.manyButtons[i]);
            }
        }
    }
}
```

まずはフォームの中に、動的に作るボタンを配列でメンバ変数として用意します。

```c#
private Button[] manyButtons;
```

続いて、動的に作るボタンは配列なので、配列そのものと各要素のボタンのインスタンスを作る必要があります。

```c#
this.manyButtons = new Button[5];
```
```c#
this.manyButtons[i] = new Button();
```

ここまではオブジェクト指向で共通です。次に、そのボタンのインスタンスに、Nameや配置などのプロパティを設定していきます。

```c#
this.manyButtons[i].Name = "OriginalButton" + i;
this.manyButtons[i].Text = "ボタン" + i;
this.manyButtons[i].Location = new Point(10, 10 + i * 22);
this.manyButtons[i].Size = new Size(80, 20);
```

そして、このコードでフォームへ表示されます(˶ ･ᴗ･ )੭

```c#
this.Controls.Add(this.manyButtons[i]);
```

![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617906/5331925c-36ed-84d0-e741-6d763ed3d86c.png)

ちゃんと表示されました！！(\*˘꒳˘\*)

## 動的に追加したボタンにイベントを付与する

ボタンの動的な作成が終わったので、次はそのボタンにイベントを付与してみます！
お次は、フォーム右下に「listBox1」という、動的に追加したボタンをクリックした結果を追記するためのリストボックスを追加します。ボタン画像は少し気が変わって変えました( ˶˙ᵕ˙˶ )

![フォームデザイン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617906/b0fef78c-1b3b-1bdc-6932-96a1d3d669ad.png)

```c#:Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyTrainingCsFrm1
{
    public partial class Form1 : Form
    {
        // フォーム呼び出しで作成するボタン(Windowsフォームのボタン - Buttonを継承する)
        private Kazumi75Button[] manyButtons;

        // 配列の要素数(ここでは5個)
        private const int ElementNum = 5;

        public Form1()
        {
            InitializeComponent();
            this.manyButtons = null;
        }

        // フォームを呼び出すボタン
        private void formCallButton_Click(object sender, EventArgs e)
        {
            if (this.manyButtons != null)
            {
                MessageBox.Show("フォームはすでに表示されています");
                return;
            }

            // ボタンの各メッセージはここであらかじめ設定する
            string[] msgs = new string[ElementNum];
            msgs[0] = "浦賀";
            msgs[1] = "鎌倉";
            msgs[2] = "三崎";
            msgs[3] = "観音崎";
            msgs[4] = "横須賀中央";
            

            // ボタンの作成(Windowsフォームのボタンを継承する)
            this.manyButtons = new Kazumi75Button[ElementNum];
            for (int i = 0; i < this.manyButtons.Length; i++)
            {
                // インスタンス作成
                this.manyButtons[i] = new Kazumi75Button();
                
                // 名前とテキストのプロパティを設定
                this.manyButtons[i].Name = "OriginalButton" + i;
                this.manyButtons[i].Text = "ボタン" + i;

                // ボタンクリック時に参照するリストボックスを指定
                this.manyButtons[i].targetLbox = listBox1;

                // メッセージを設定
                this.manyButtons[i].buttonMsg = msgs[i];

                // サイズと配置
                this.manyButtons[i].Size = new Size(100, 20);
                this.manyButtons[i].Location = new Point(10, 10 + i * 22);

                // フォームへの追加
                this.Controls.Add(this.manyButtons[i]);

                // クリック時のボタンごとのイベント動作を作成する
                this.manyButtons[i].eventMaking();
            }
        }
    }
}
```

```c#:Kazumi75Button.cs
using System;
using System.Windows.Forms;

namespace MyTrainingCsFrm1
{
    // オリジナルのボタンを定義する(WindowsフォームのButtonを継承)
    public class Kazumi75Button : Button
    {
        // ボタンを押して表示される文言テキスト
        public string buttonMsg { get; set; }

        // リストボックスを参照させる
        public ListBox targetLbox { get; set; }

        // ボタンへのイベントをセットする
        public void eventMaking()
        {
            this.Click += new EventHandler(doClickEvent);
        }

        // ボタンへのイベントを解除する
        public void eventSuspend()
        {
            this.Click -= new EventHandler(doClickEvent);
        }

        // クリックイベントの実体(参照するリストボックスに文言テキストを追加)
        public void doClickEvent(object sender, EventArgs e)
        {
            this.targetLbox.Items.Add(this.buttonMsg);
        }
    }
}
```

ここでは、**ボタンButtonの継承のKazumi75Buttonというボタンクラスを作成しています**。なぜなら、ボタンクリック時に、ボタンごとが用意する文字列を、ボタンクリックイベントで利用するからです。さらに、ボタン本体は結果を表示させるためのlistBox1を参照できるようにするため、その参照すべきリストボックスを受け持つ必要があるからです。かなり言い方が難しいですが…(੭ु˙꒳˙)੭ु Form1.csのほうにクリックイベントを付けたほうがよかったかな？？判断がちょっと難しいところ。。。

```c#
private Kazumi75Button[] manyButtons;
private const int ElementNum = 5;
　…(中略)…
this.manyButtons = new Kazumi75Button[ElementNum];
```

これはまずWindowsフォームのボタンを継承したKazumi75Buttonの配列を5個定義。今は5個ボタンを動的に作成して、それらにクリックイベントを付与していきます

```c#
this.manyButtons[i] = new Kazumi75Button();
```

ここでインスタンスを作成し、それ以降はButtonのようにプロパティ設定(listBox1への参照も忘れずに)とフォームへの配置を行っています。そこで、今回のポイントですが！！まずインスタンスを作成する前の配列代入の処理

```c#
string[] msgs = new string[ElementNum];
msgs[0] = "浦賀";
msgs[1] = "鎌倉";
msgs[2] = "三崎";
msgs[3] = "観音崎";
msgs[4] = "横須賀中央";
　…(中略)…
this.manyButtons[i].buttonMsg = msgs[i];
```

これは「*this.manyButtons[i].buttonMsg = msgs[i]*」でボタンごとの文字列をセットするときに使う文字列で、現在はプログラムのソースの中ですが、そこの箇所は今後データベースや設定ファイルで置き換える想定でいます。これらの文字列は、ボタンクリックのイベントの中で使います。次に、

```c#
this.manyButtons[i].eventMaking();   // Form1.cs
```
```c#
public void eventMaking()            // Kazumi75Button.cs
{
    this.Click += new EventHandler(doClickEvent);
}
// クリックイベントの実体(参照するリストボックスに文言テキストを追加)
public void doClickEvent(object sender, EventArgs e)
{
    this.targetLbox.Items.Add(this.buttonMsg);
}
```

動的に定義したボタンに、クリックイベントを追加して、クリックすると結果を表示するlistBox1へボタンごとの文字列を追加するという箇所。

多少ややこしいやりとりになってきましたが。。。(\*´꒳`\*;)申し訳ないです

![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617906/30fa8933-b5ec-d4a5-f0ca-26a13f911374.png)

実行結果は、ボタンごとに受け持つ文字列がちゃんと右下のフォームに追加されている(˶ ･ᴗ･ )੭⚐

## ボタンの数を増やしてみる

今回はプログラム上ですが、今度は要素数を5から8に増やして、数を増やしても動的にボタンが増えて、イベントも反応してくれるかチェック

```c#:Form1.csの修正箇所
// 配列の要素数(ここでは8個)
private const int ElementNum = 8;

　…(中略)…

string[] msgs = new string[ElementNum];
msgs[0] = "浦賀";
msgs[1] = "鎌倉";
msgs[2] = "三崎";
msgs[3] = "観音崎";
msgs[4] = "横須賀中央";
msgs[5] = "城ヶ島";
msgs[6] = "七里ガ浜";
msgs[7] = "馬堀";
```

![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617906/4573798b-624e-fe83-289b-6fce9c59adcf.png)

「城ヶ島」「七里ガ浜」「馬堀」が追加した分なので、これもちゃんと表示してくれました


# 次回
ボタンは一通り動的追加できたので、テキストボックスを動的に追加して、それらを動的に参照する方法を検証してみます♡

# 参照文献

- [【.NET】コントロールを実行時に作成する](https://dobon.net/vb/dotnet/control/addcontrol.html)
- [【.NET】イベントを動的に登録する](http://www.r-nakai.com/archives/19)
- [コントロール名を表す文字列からコントロールを取得する - 文字列名のstring型からのコントロールのアクセス - C#プログラミング](https://www.ipentec.com/document/csharp-get-control-by-name-of-control-string)
- [C#_プログラムから生成した ( 動的 ) コントロールにイベントをつける](http://koshinran.hateblo.jp/entry/2017/11/17/193251)
- [Control.ControlCollection.Find(String, Boolean) メソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.controlcollection.find?view=netcore-3.1) - Microsoft .NET 公式
- [Windowsフォーム上のコントロールを検索するには？［2.0以降、C#、VB］](https://www.atmarkit.co.jp/fdotnet/dotnettips/479ctrlfind/ctrlfind.html) - atmark IT