WinFormsで学ぶインスタンス ― Designerが書いたコードを自分で書いてみよう

広告

前回の記事「GitHub Desktopで見てみよう ― WinFormsでボタンを置くと、裏では何が変わる?」では、WinFormsデザイナーでButtonを置くと、Form1.Designer.csに次のようなコードが自動生成されることを、GitHub Desktopの差分から確認しました。

button1 = new Button();
button1.Text = "button1";
Controls.Add(button1);
button1.Click += button1_Click;

これは、デザイナーにしか書けない特別なコードではありません。今回は、同じことを自分の手で書いてみます。

そして最後に自分でクラスを作り、「クラス」と「インスタンス」というオブジェクト指向の基本につなげていきます。

この記事の流れ

  1. 自分でButtonを作る
  2. 何度もクリックして、Buttonが増えることを確かめる
  3. 実行時に作ったButtonにも、Clickイベントを付ける
  4. GitHub Desktopで、今回はどのファイルが変わったのかを見る
  5. 自作のStudentクラスでも、同じことをしてみる

準備

前回の記事で作ったプロジェクト(button1とlabel1を置いたForm)を、そのまま使います。

まずは、今の状態をGitHub Desktopでコミットしておきましょう。こうしておくと、このあと自分で書いたコードだけが差分に表示されます。

1.自分でButtonを作ってみる

Form1.csを開き、button1_Clickの中身を次のように書き換えます。メソッドの上にあるcountの行も追加してください。

private int count = 0;

private void button1_Click(object sender, EventArgs e)
{
    count++;

    Button newButton = new Button();
    newButton.Text = $"ボタン{count}";
    newButton.Location = new Point(20, count * 55);
    newButton.Size = new Size(150, 46);

    Controls.Add(newButton);
}

実行して、button1を3回クリックしてみましょう。

実行時に作ったボタン1〜3が並んだフォーム
button1を3回クリックすると、「ボタン1」「ボタン2」「ボタン3」が作られる

クリックするたびに、「ボタン1」「ボタン2」「ボタン3」とButtonが増えていきます。

プログラムを起動したときには存在しなかったButtonが、クリックした瞬間に画面に現れました。

前回のDesigner.csと見比べてみる

今書いたコードを、前回デザイナーが生成したコードと並べてみます。

デザイナーが書いたコード(Form1.Designer.cs)自分で書いたコード(Form1.cs)
button1 = new Button();Button newButton = new Button();
button1.Location = new Point(325, 199);newButton.Location = new Point(20, count * 55);
button1.Size = new Size(150, 46);newButton.Size = new Size(150, 46);
button1.Text = "button1";newButton.Text = $"ボタン{count}";
Controls.Add(button1);Controls.Add(newButton);

やっていることは同じです。

Buttonを生成 → プロパティを設定 → FormのControlsに追加

デザイナーは魔法を使っていたわけではなく、このコードを代わりに書いてくれていただけでした。

なお、Sizeは前回デザイナーが生成した値と同じnew Size(150, 46)にしています。指定しないと、画面の拡大率の設定によっては、デザイナーで置いたボタンより小さく表示されることがあります。

違いは「いつ作られるか」

ひとつだけ大きな違いがあります。

デザイナーで置いたbutton1は、アプリの起動時(InitializeComponent()の中)に作られます。今回のButtonは、button1をクリックしたとき、つまりアプリの実行中に作られます。

そのため、アプリを終了してもう一度起動すると、作ったButtonはなくなっています。Form1.Designer.csには何も書かれていないので、起動した直後にはまだ存在しないのです。

2.なぜButtonが増えるのか

ポイントは次の1行です。

Button newButton = new Button();

new Button()によって、Buttonクラスから新しいオブジェクトが作られます。このように、あるクラスをもとに作られた具体的なオブジェクトを、そのクラスのインスタンスと呼びます。

button1をクリックするたびにbutton1_Clickが実行され、そのたびにnew Button()も実行されます。3回クリックすれば、3つの別々のインスタンスが作られます。

Buttonクラス
    │
    ├── new(1回目)→ インスタンス  Text = "ボタン1"
    ├── new(2回目)→ インスタンス  Text = "ボタン2"
    └── new(3回目)→ インスタンス  Text = "ボタン3"

どれも同じButtonクラスから作られていますが、Textや位置はそれぞれ違います。1つのクラスから、状態の違うインスタンスをいくつでも作れるということです。

何度もクリックし続けると、やがてButtonはフォームの下にはみ出して見えなくなります。見えなくなってもButtonは作られていて、表示できる範囲の外に置かれているだけです。

補足:countをメソッドの外に書いた理由(クリックで開く)
  • countbutton1_Clickの中で宣言すると、メソッドが呼ばれるたびに0から作り直されます。そのため、何回押しても「ボタン1」になってしまいます。メソッドの外(クラスのフィールド)に書くと、Formが開いている間は値が保たれます。
  • 一方、変数newButtonは、メソッドが終わると使えなくなります。それでもButtonが消えないのは、Controls.Addによって、FormがそのButtonを持ち続けているからです。

3.実行時に作ったButtonにもClickイベントを付ける

作ったButtonは、今のままではクリックしても何も起きません。

前回の記事で、デザイナーが次の1行を書いていたことを思い出してください。

button1.Click += button1_Click;

同じことを、自分で書きます。button1_Clickに1行追加し、NewButton_Clickメソッドを新しく作ります。

private int count = 0;

private void button1_Click(object sender, EventArgs e)
{
    count++;

    Button newButton = new Button();
    newButton.Text = $"ボタン{count}";
    newButton.Location = new Point(20, count * 55);
    newButton.Size = new Size(150, 46);
    newButton.Click += NewButton_Click;

    Controls.Add(newButton);
}

private void NewButton_Click(object sender, EventArgs e)
{
    Button clicked = (Button)sender;
    label1.Text = $"{clicked.Text}が押されました";
}

実行して、button1を3回クリックしてから、「ボタン2」をクリックしてみましょう。

ボタン2を押してlabel1に「ボタン2が押されました」と表示されたフォーム
「ボタン2」を押すと、label1にそのボタンの名前が表示される

label1に「ボタン2が押されました」と表示されます。「ボタン1」や「ボタン3」を押せば、それぞれの名前が表示されます。

1つのメソッドで、どのボタンか分かる理由

3つのButtonは、すべて同じNewButton_Clickを呼び出します。それでも、どのButtonが押されたのかが分かるのは、引数senderのおかげです。

senderには、イベントを起こしたオブジェクト(今回は、押されたButtonのインスタンス)が入っています。型はobjectなので、(Button)senderでButtonとして扱えるように変換してから、Textを読み取っています。

前回の記事では説明せずに通り過ぎたobject senderは、「誰がこのイベントを起こしたのか」を教えてくれる引数だったのです。

ここでも、3つのButtonが別々のインスタンスであることが分かります。同じクラスから作られていても、それぞれが自分のTextを持っています。

4.GitHub Desktopで差分を見てみる

ここまでの変更を、GitHub Desktopで確認してみましょう。

📷【キャプチャー③:GitHub DesktopのChanges一覧(Form1.csだけが変更されている)】

前回と違って、変更されたファイルはForm1.csだけです。Form1.Designer.csは変更されていません。

📷【キャプチャー④:Form1.csの差分(count、newButton関連、NewButton_Clickの追加)】

操作変更されたファイル
デザイナーでButtonを置く(前回)Form1.Designer.cs
自分でnew Button()を書く(今回)Form1.csのみ

デザイナーで置いたコントロールは、起動時に作られるようForm1.Designer.csに書き込まれます。自分でForm1.csに書いたコードは、書いた場所で、書いたタイミングで実行されます。

どちらもnew Button()であることは同じですが、どこに書くかで「いつ作られるか」が変わることが、差分からも読み取れます。

確認できたら、ここでコミットしておきましょう。

5.Buttonは誰が作ったクラス?

ここまで、当たり前のようにnew Button()と書いてきました。では、Buttonクラスは誰が作ったのでしょうか。

Buttonクラスは、Microsoftが作り、.NETの一部として用意してくれているクラスです。私たちはそれを使って、インスタンスを作っていました。

クラスは、自分で作ることもできます。次は、自作のクラスで同じことをしてみましょう。

6.自分でStudentクラスを作ってみる

ソリューションエクスプローラーでプロジェクトを右クリックし、「追加」→「クラス」を選びます。名前をStudent.csにして追加します。

作成されたファイルを、次のように書き換えます。

namespace WinFormsApp1
{
    internal class Student
    {
        public string Name { get; set; } = "";

        public string GetGreeting()
        {
            return $"こんにちは、{Name}です";
        }
    }
}

ファイルの先頭にusingの行がいくつか生成されていても、そのままで構いません。また、Nameの後ろの= ""は、最初の値を空の文字列にしておくためのものです。これを書かないと、null許容に関する警告が表示されることがあります。

Buttonと比べてみましょう。

  • Nameプロパティです。ButtonのTextLocationと同じように、インスタンスごとの状態を持ちます。
  • GetGreeting()メソッドです。インスタンスができること(振る舞い)を表します。

Studentのインスタンスを作る

Form1.csのコンストラクター(InitializeComponent();の下)に、次のコードを追加します。

public Form1()
{
    InitializeComponent();

    Student student1 = new Student();
    student1.Name = "田中";

    Student student2 = new Student();
    student2.Name = "佐藤";

    label1.Text = student1.GetGreeting() + "\n" + student2.GetGreeting();
}

実行すると、label1に2人の挨拶文が表示されます。

label1に田中さんと佐藤さんの挨拶が表示されたフォーム
2つのStudentインスタンスが、それぞれ違う挨拶を返している

student1とstudent2は、同じStudentクラスから作られた別々のインスタンスです。だから、それぞれが違うNameを持ち、GetGreeting()の結果も変わります。

なお、GetGreeting()は文字列を返すだけで、画面への表示はForm側で行っています。Studentは「生徒の情報」を表すクラスなので、画面のことはFormに任せています。このように役割を分けておくと、同じStudentクラスを、別の画面や別のアプリでも使えます。

GitHub Desktopで見ると

Student.csが新しいファイルとして追加されたGitHub Desktopの差分
Student.csは新しいファイルなので、すべての行が緑(追加)で表示される

今回は、Student.csが新しいファイルとして追加され、中身がすべて緑(追加)で表示されます。Form1.csには、Studentを使うコードの追加分が表示されます。

Studentを使うコードが追加されたForm1.csの差分
Form1.csには、Studentのインスタンスを作るコードが追加されている
やったことGitHub Desktopで見えた変化
デザイナーでButtonを置く(前回)Form1.Designer.csが変わる
自分でnew Button()を書く(今回の前半)Form1.csだけが変わる
自分でクラスを作る(今回の後半)新しいファイル(Student.cs)が増える

ButtonとStudentを並べてみる

ButtonStudent
誰が作ったクラス?Microsoft(.NET)自分
インスタンスの作り方new Button()new Student()
状態(プロパティ)TextLocationSizeなどName
振る舞いClickイベント、Focus()などのメソッドGetGreeting()
複数作ると?それぞれ違うTextを持つそれぞれ違うNameを持つ
Button
 ↓
.NETが用意したクラス
 ↓ new Button()
Buttonのインスタンス
 ↓
Text などの状態を持つ/Click などの振る舞いがある

Student
 ↓
自分で作ったクラス
 ↓ new Student()
Studentのインスタンス
 ↓
Name などの状態を持つ/GetGreeting() などの振る舞いがある

Buttonでやっていたことと、Studentでやったことは同じです。ここまでが、C#でオブジェクト指向を理解するための大切な第一歩です。

  • クラス:どんなデータ(状態)と、どんな処理(振る舞い)を持つかを決めたもの。ButtonやStudent。
  • インスタンス:クラスからnewで作った実体。newButton、student1、student2。
  • プロパティ:インスタンスが持つ状態。TextNameなど。
  • メソッド:インスタンスができること。GetGreeting()など。

「クラスは設計図、インスタンスは実体」という説明をよく見かけます。今回の実験で、それは暗記するための言葉ではなく、ButtonやStudentを使って実際にやっていたことだと分かったはずです。

まとめ

やったこと分かったこと
自分でnew Button()を書いたデザイナーが生成していたコードは、自分でも書ける
何度もクリックしたnewするたびに、別のインスタンスが作られる
Click +=で実行時のButtonにイベントを付けた1つのメソッドでも、senderでどのインスタンスか分かる
GitHub Desktopで差分を見た自分で書いたコードはForm1.csにだけ現れる
Studentクラスを作った自作のクラスでも、Buttonと同じようにインスタンスを作れる

前回は「デザイナーで操作する → 差分を見る」で、自動生成されたコードの意味を知りました。今回は「同じコードを自分で書く → 実行して確かめる」で、クラスとインスタンスの関係を体験しました。

WinFormsの画面作りと、C#のオブジェクト指向は、別々の学習ではありません。画面に置いていたButtonも、自分で作ったStudentも、同じ仕組みで動いています。

次のステップ:継承へ

今回は、クラスからインスタンスを作るところまでを確かめました。

次は、「なぜButtonにはあんなにたくさんのプロパティがあるのか」という疑問から、クラスどうしの関係である継承へ進みます。

WinFormsで学ぶ「継承」入門(シリーズ目次)

デザイナが生成するコードをもっと詳しく知りたい場合は、WinFormsの「ドラッグ&ドロップ」の正体(シリーズ目次)もあわせてどうぞ。

訪問数 14 回, 今日の訪問数 14回

広告