WindowsFormsアプリを分解してみる

2023年2月8日

Windowsアプリケーションを作成するのに一番やさしいのは、WindowsFormアプリのテンプレートをベースにすることです

WindowsFromsアプリとは

WinForms (Windows Forms) は、Microsoft .NET Framework(または.NET)に含まれる、Windowsデスクトップアプリケーションの開発フレームワークなのです

この開発フレームワークを使って作られたアプリケーションをWindowsFormsアプリケーションといいます

Windows Formsアプリケーションは、グラフィカルユーザーインターフェース(GUI)を提供することで、ユーザーがコンピューター上でのタスクを実行するためのユーザフレンドリーな方法を提供します。 Windows Formsアプリケーションは、C#、VB.NET、および他の言語を使用して開発することができます

ファイル構成、ソリューション構成

WindowsFormsアプリをテンプレートとしてVisualStudioを起動した時の最初に自動作成されるフォルダとファイル、またソリューション、プロジェクトの構成についてみていきましょう

表示の切り替え方法

フォルダービュー(ファイル一覧、構成)とソリューションビューを切り替える方法です
アイコンのクリックで選択ができます

フォルダービュー(ファイル構成)

エクスプローラでの表示と一致する構成を表現できます

ソリューション構成

ソリューションとしての管理で、関連情報から構成を表現されています
ファイルの構成と必ずしも一致しているわけではありません

初期に作成される主なファイル

新しくFormsアプリを作成すると次のファイルとコードが自動的に作成されます

Form1.csファイル

using System;
using System.Windows.Forms;

namespace WinFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

このコードでは、名前空間 WinFormsApp の中に、Formクラスを継承(拡張)した Form1 クラスが定義されています。また、 InitializeComponent メソッドが呼び出され、このフォームの初期設定が行われます。

このコードを実行すると、WinFormsのアプリケーションが起動し、画面上に空のフォームウィンドウが表示されます。

クラス宣言

public partial class Form1 : Form

この行は、 Form1 クラスを定義しています。このクラスは System.Windows.Forms.Form クラスを継承しています。

partial 修飾子が付いていることから、このクラスは複数のソースファイルに分割されていることが考えられます。このようなクラスを “partial class" と呼びます。各ソースファイルにはクラスの一部を定義する必要があります。最終的に、すべてのソースファイルを結合することで、完全なクラスが作成されます。ォームアプリでは次のForm1.Designer.csファイルにもう一方のForm1クラスが存在します

このクラスは Windows Forms アプリケーションで利用するための主要な画面を表すフォームを定義するために使用されます。このクラスはグラフィックスインターフェイス(GUI)を定義するために、様々なコントロール (ボタン、テキストボックス、ラベルなど) を含むことができます。また、このクラスはユーザーアクション (ボタンのクリックなど) を処理するためにイベントハンドラーを含むことができます。

Form1.Designer.csファイル

VisualStudioがプログラマーのコントロールのドラッグ&ドロップなどの操作によって自動的に更新するファイルです

partial class Form1
{
    private void InitializeComponent()
    {
        this.SuspendLayout();
        // 
        // Form1
        // 
        this.ClientSize = new System.Drawing.Size(300, 300);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);
    }
}

このコードでは、 ClientSize プロパティが 300x300 のサイズに設定され、 Name プロパティが “Form1″、 Text プロパティが “Form1" に設定されています。また、 SuspendLayoutResumeLayout メソッドで、レイアウトの一時停止と再開が行われます。

この InitializeComponent メソッドをVisualStudioが編集することで、フォームの外観や動作などをカスタマイズすることができます。なので、プログラマーは編集してはいけません

クラス宣言

partial class Form1

この行は、 Form1 クラスを定義しています。Form1.csファイルにも同じクラス名のクラスが定義されています
partialキーワードがついていますので、2つのファイルが分割されているのがわかります

フォームのサイズについて

this.ClientSize = new System.Drawing.Size(300, 300);

ClientSize プロパティは、フォームまたはコントロール内に配置されたコントロールのクライアント領域の大きさを指定します。つまり、クライアント領域は、フォームまたはコントロールに含まれるコントロールの大きさを示すものであり、ボーダー、スクロールバー、その他の構成要素は含まれません。

一方、Sizeプロパティは、フォームまたはコントロールの大きさ(全体的な大きさ)を指定するものであり、ボーダー、スクロールバー、その他の構成要素も含まれます。

結論として、ClientSize は、コントロールまたはフォーム内で使用可能なスペースを指定するのに対し、Sizeはコントロールまたはフォーム全体の大きさを指定するものです。

Program.csファイル

アプリケーション起動時、プログラムはどこから始まるのか?

コンソールアプリでは、Mainメソッドから始まることはご存知の通りです
(トップレベルステートメントで記述しても、内部ではMainメソッドから実行されるようにみなされています)
では、WindowsFormsアプリではどうでしょう

もちろん、Mainから始まります
Program.csファイルにMainメソッドが定義されています

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    internal static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

このコードはWinFormsアプリケーションのエントリポイントを定義しています。

[STAThread]属性は、アプリケーションが単一スレッドアパートメント (STA) スレッドで実行されることを示します。

Application.EnableVisualStyles()は、Windows Vista以降で使用可能なグラフィックススタイルを有効にします。

Application.SetCompatibleTextRenderingDefault(false)は、テキストのレンダリングにGDI+を使用するように指定します。

最後に、Application.Run(new Form1());は、WinFormsアプリケーションを起動して、Form1を開始フォームとして使用するように指定します。

ボタンを追加した時のコード

WinFormsアプリケーションにボタンを追加する方法は、WinForms Designerまたはコードを使って行うことができます。

WinForms Designerを使った方法は次のようになります。

  1. ツールボックスからボタンをフォーム上にドラッグ&ドロップします
  2. フォーム上でボタンを選択します。
  3. プロパティウィンドウで、ボタンのプロパティを設定します。
  4. ボタンをダブルクリックして、イベントハンドラーを作成します。
  5. イベントハンドラのブロックにボタンを押された時の処理を記述します

この方法で追加されたボタンのC#コードは次のようになります。

Form1.Designer.csファイルのForm1クラス内の一部抜粋

private void InitializeComponent()
{
    this.button1 = new System.Windows.Forms.Button();
    this.SuspendLayout();
    // 
    // button1
    // 
    this.button1.Location = new System.Drawing.Point(100, 100);
    this.button1.Name = "button1";
    this.button1.Size = new System.Drawing.Size(75, 23);
    this.button1.TabIndex = 0;
    this.button1.Text = "Button";
    this.button1.UseVisualStyleBackColor = true;
    this.button1.Click += new System.EventHandler(this.button1_Click);
    // 
    // Form1
    // 
    this.ClientSize = new System.Drawing.Size(300, 300);
    this.Controls.Add(this.button1);
    this.Name = "Form1";
    this.Text = "Form1";
    this.ResumeLayout(false);
}
private System.Windows.Forms.Button button1;

コメントを追加すると次のようになります

private void InitializeComponent()
{
    // ボタンのインスタンスを作成する
    this.button1 = new System.Windows.Forms.Button();
    this.SuspendLayout();
    // 
    // button1
    // 
    // ボタンの位置を指定する
    this.button1.Location = new System.Drawing.Point(100, 100);
    // ボタンの名前を設定する
    this.button1.Name = "button1";
    // ボタンのサイズを指定する
    this.button1.Size = new System.Drawing.Size(75, 23);
    // ボタンのTabインデックスを設定する
    this.button1.TabIndex = 0;
    // ボタンのテキストを設定する
    this.button1.Text = "Button";
    // ボタンのスタイルを指定する
    this.button1.UseVisualStyleBackColor = true;
    // ボタンのクリックイベントハンドラを追加する
    this.button1.Click += new System.EventHandler(this.button1_Click);
    // 
    // Form1
    // 
    // フォームのサイズを指定する
    this.ClientSize = new System.Drawing.Size(300, 300);
    // フォームにボタンを追加する
    this.Controls.Add(this.button1);
    // フォームの名前を設定する
    this.Name = "Form1";
    // フォームのタイトルを設定する
    this.Text = "Form1";
    this.ResumeLayout(false);
}
// ボタンのフィールドを定義する
private System.Windows.Forms.Button button1;

このコードでは、 button1 という名前のボタンが作成され、 Location プロパティが 100,100 の位置に設定されます。また、ボタンのクリックイベントハンドラーとして button1_Click メソッドが定義されています

Form1.csファイルのForm1クラス内の一部

このイベントハンドラ(メソッド)内では、ボタンがクリックされたときに “Button clicked!" というメッセージボックスが表示されます。

private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("Button clicked!");
}

System.Windows.Forms.Buttonクラスのコードの実装は?

System.Windows.Forms.Button クラスは、WinFormsアプリケーションで使用されるボタンを表すクラスです。このクラスは、 System.Windows.Forms 名前空間内に含まれており、Windowsユーザーインターフェイスを構築するための多数のコントロールを含んでいます。

このクラスは、次のような内部構造を持っています。

  • プロパティ:ボタンの状態や外観を決定するための設定を含みます。例えば、 LocationSizeTextForeColor などがあります。
  • メソッド:ボタンの操作を実行するための方法を含みます。例えば、 PerformClickSelect などがあります。
  • イベント:ボタンがクリックされたり、選択されたりすると発生するイベントを含みます。例えば、 ClickMouseEnter などがあります。

このクラスは、WinFormsアプリケーションでボタンを作成し、操作するための主要なクラスです。アプリケーションに必要な機能を提供するために、このクラスを継承した独自のクラスを作成することもできます。

Buttonクラスの継承関係

System.Windows.Forms.Button クラスは、 System.Windows.Forms 名前空間内に含まれており、WinFormsアプリケーションで使用されるボタンを表すクラスです。このクラスは、以下のようなクラス階層を継承しています。

  1. System.Object
  2. System.ComponentModel.Component
  3. System.Windows.Forms.Control
  4. System.Windows.Forms.ButtonBase
  5. System.Windows.Forms.Button

このクラス階層には、WinFormsアプリケーションで利用できるコントロールの汎用的な機能が定義されています。例えば、位置やサイズ、テキストなどの共通的なプロパティが定義されています。

System.Windows.Forms.Button クラスを継承することで、独自のボタンクラスを作成することもできます。これにより、アプリケーションに必要な独自の機能を提供することができます。

System.Windows.Forms.Button クラスから自作ボタンのインスタンス(実体)を作成するサンプル

上記の分析から、自作のボタンを作成してみます
以下、次のようなポイントで作っていきます

参考にするコードVisualStudioが自動作成するコード
作成したコード自作のために加工したコード

宣言

次のコードを参考にmyButtonを宣言します

参考にするコード

private System.Windows.Forms.Button button1;

作成したコード

System.Windows.Forms名前空間はusingに設定するため省略します

private Button myButton;

インスタンスの作成

ButtonクラスからButtonのインスタンスを作成します

参考にするコード

this.button1 = new System.Windows.Forms.Button();

作成したコード

thisはform1のインスタンスであろことを示していますが、省略できます
System.Windows.Forms名前空間はusingに設定するため省略します

myButton = new Button();

フォームに表示できるようにします

フォームに表示するには、Formクラス(厳密にはControlクラス)が持っているControlsにAddメソッドで追加します

参考にするコード

this.Controls.Add(this.button1);

作成したコード

thisはform1のインスタンスであろことを示していますが、省略できます

Controls.Add(myButton);

myButtonのいくつかのプロパティを変更

ボタンの表示される文字とボタンの大きさを変更します

参考にするコード

this.button1.Text = "Button";
this.button1.Size = new System.Drawing.Size(75, 23);

作成したコード

thisはform1のインスタンスであろことを示していますが、省略できます
System.Drawing名前空間はusingに設定するため省略します

myButton.Text = "自作ボタン";
myButton.Size = new Size(100, 30);

クリックした時の動作を追加(イベント)

クリックされたら「クリック」と表示されるようにします

参考にするコード

this.button1.Click += new System.EventHandler(this.button1_Click);

private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("Button clicked!");
}

作成したコード

thisはform1のインスタンスであろことを示していますが、省略できます
System名前空間はusingに設定するため省略します

myButton.Click += new EventHandler(OnMyButtonClick);

private void OnMyButtonClick(object sender, EventArgs e)
{
    MessageBox.Show("クリック");
}

今のC#バージョンはnew EventHandler()も省略できます

myButton.Click += OnMyButtonClick;

private void OnMyButtonClick(object sender, EventArgs e)
{
    MessageBox.Show("クリック");
}

作成したコードを合わせます

これまでのコードを合わせます

全体のコード

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WinFormsApp
{
    public partial class Form1 : Form
    {
        // 自作ボタン用の変数
        Button myButton;  

        public Form1()
        {
            InitializeComponent();

            // ボタンのインスタンスを作成
            myButton = new Button();  
            // フォームにボタンを追加
            Controls.Add(myButton);  
            // ボタンにテキストを設定
            myButton.Text = "自作ボタン";  
            // ボタンのサイズを設定
            myButton.Size = new Size(100, 30);  
            // クリックイベントにハンドラを登録
            myButton.Click += OnMyButtonClick;  

            void OnMyButtonClick(object sender, EventArgs e)
            {
                // クリック時にメッセージボックスを表示
                MessageBox.Show("クリック");  
            }
        }
    }
}

解説

このコードはWindowsフォームアプリケーションの一部であり、Form1クラスを定義しています。このクラスは画面にボタンを表示するためのものです。

InitializeComponent()メソッドは、自動生成されたコードを初期化するものです。

myButton変数はButton型で、新しいボタンのインスタンスを作成しています。このボタンはControlsコレクションに追加され、テキストに「自作ボタン」が設定されます。サイズもSize(100, 30)として指定されています。

最後に、ボタンのクリックイベントに対するハンドラ(OnMyButtonClickメソッド)が設定されています。このメソッドは、ボタンがクリックされた際に「クリック」というメッセージボックスを表示する処理を実行します

実行結果

System.Windows.Forms.Button クラスを継承した自作クラスのサンプル

using System.Windows.Forms;
using System.Drawing;

class MyButton : Button
{
    public MyButton()
    {
        this.Text = "MyButton";
        this.ForeColor = System.Drawing.Color.Red;
    }
}

このサンプルコードでは、新しいボタンクラス MyButton を作成しています。このクラスは、 System.Windows.Forms.Button クラスを継承しています。また、新しいボタンのテキストと前景色を設定するコンストラクタも定義されています。この新しいボタンクラスを使用すると、アプリケーションに必要な独自の機能を提供することができます。

クリックするとカウントアップするプロパティを追加してみましょう

using System.Windows.Forms;

class MyButton : Button
{
    private int count = 0;

    public MyButton()
    {
        this.Text = "MyButton";
        this.ForeColor = System.Drawing.Color.Red;
        this.Click += new System.EventHandler(this.MyButton_Click);
    }

    private void MyButton_Click(object sender, System.EventArgs e)
    {
        count++;
        this.Text = "MyButton " + count;
    }
}

このサンプルコードでは、新しいボタンクラス MyButton に、プライベートな変数 count を追加しています。この変数は、ボタンがクリックされるたびにインクリメントされます。また、ボタンクリックイベントハンドラ MyButton_Click も追加されています。このイベントハンドラでは、ボタンのテキストを「MyButton 」に続けて count の値を追加して更新されます。

this.Click += new System.EventHandler(this.MyButton_Click);の仕組みは

this.Click += new System.EventHandler(this.MyButton_Click); の行は、イベントハンドラをボタンクリックイベントに追加するものです。

  • this.Click は、現在のボタンインスタンスのクリックイベントを参照します。
  • += 演算子は、イベントハンドラをイベントに追加するものです。これにより、イベントが発生したときに呼び出される関数を指定できます。
  • •new System.EventHandler(this.MyButton_Click) は、イベントハンドラを作成するものです。 System.EventHandler のインスタンスは、 MyButton_Click 関数を参照します。

以上のように、 this.Click += new System.EventHandler(this.MyButton_Click); 行は、ボタンクリックイベントに対して MyButton_Click

動作するように調整したコード

全体のコード

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            MyButton mybutton = new MyButton();
            mybutton.Parent = this;
        }
    }
    class MyButton : Button
    {
        private int count = 0;

        public MyButton()
        {
            AutoSize = true;
            Text = "MyButton";
            ForeColor = Color.Red;
            Click += new EventHandler(MyButton_Click);
        }

        public void MyButton_Click(object sender, EventArgs e)
        {
            count++;
            Text = "MyButton " + count;
        }
    }
}

解説

このコードは、Windows Formsアプリケーションを作成するものです。

  • Form1 クラスは、フォームのメインクラスです。
  • InitializeComponent 関数は、初期化を行い、初期化済みコンポーネントを作成します。
  • MyButton クラスは、Button クラスを継承したユーザー定義のボタンクラスです。
  • MyButton クラスのコンストラクタでは、ボタンのプロパティを初期化します。
  • MyButton_Click 関数は、ボタンがクリックされたときに呼び出される関数です。この関数では、カウントをインクリメントし、ボタンのテキストを更新します。

以上のように、このコードでは、ボタンクラスを定義し、 Form1 クラスから作成し、 Click イベントハンドラを登録していますが、実際にはこのイベントハンドラは呼び出されません。

mybutton.Parent = this;

mybutton.Parent = this; では、作成したインスタンスの親フォームを Form1 クラスのインスタンス (this) に設定しています。これにより、ボタンは Form1 上に配置され、 Form1 ウィンドウに表示されます。

実行結果

おまけ

フォームにボタンを追加する2つの方法

次の2つはともForm1にボタンを追加するコードになります

これらの2つの方法の違いは、追加方法が異なる点です。両者を使用することで同じ結果が得られます。

コードの比較

myButton.Parent = this;

myButton.Parentプロパティにthisを設定して、ボタンをフォーム(ここではForm1クラス)に関連付けています。この方法は、ボタンをフォームに追加する際にControlsコレクションを使用することなく、直接フォームとの関連付けを行う方法です。

Controls.Add(myButton);

ControlsコレクションにmyButtonを追加して、ボタンをフォームに追加しています。この方法は、一般的な方法で、Controlsコレクションを使用して、フォームに要素を追加することができます。

フレームワークの内部コードを見てみる

次のコードは、.NET FrameworkのSystem.Windows.Forms.Controlクラスの一部です。Controlクラスは、Windows Formsアプリケーションのユーザーインターフェイスに使用される要素(例えばボタン、テキストボックスなど)の継承元になります

このParentプロパティは、コントロールの親コントロールまたは親コンテナーを表します。このプロパティを設定することで、このコントロールを他のコントロールの中に含めることができます。このプロパティは探索不可能にマークされていますが、内部的には変更可能です。

IntSecurity.GetParent.Demand()は、親コントロールへのアクセスを制限するセキュリティチェックを行っています。

Parent(ParentInternal)プロパティのSetでvalue.Controls.Add(this);が実行されています
つまり、内部では同じことをしているのです

また、Parent = null;でparent.Controls.Remove(this);が実行されているのがわかります

//
// 概要:
//     コントロールの親コンテナーを取得または設定します。
//
// 戻り値:
//     コントロールの親コントロールまたはコンテナー コントロールを表す System.Windows.Forms.Control。
[SRCategory("CatBehavior")]
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription("ControlParentDescr")]
public Control Parent
{
    get
    {
        IntSecurity.GetParent.Demand();
        return ParentInternal;
    }
    set
    {
        ParentInternal = value;
    }
}

internal virtual Control ParentInternal
{
    get
    {
        return parent;
    }
    set
    {
        if (parent != value)
        {
            if (value != null)
            {
                value.Controls.Add(this);
            }
            else
            {
                parent.Controls.Remove(this);
            }
        }
    }
}

アノテーション(注釈)の説明

[SRCategory("CatBehavior")]
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription("ControlParentDescr")]

これらはC#アノテーションです。

  • [SRCategory(“CatBehavior")]:このアノテーションは、このコントロールのプロパティを「CatBehavior」カテゴリーにグループ化します。
  • [Browsable(false)]:このアノテーションは、このコントロールのプロパティをデザイナで表示しないことを指示します。
  • [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]:このアノテーションは、このコントロールのプロパティをシリアル化しないことを指示します。
  • [SRDescription(“ControlParentDescr")]:このアノテーションは、このコントロールのプロパティの説明を「ControlParentDescr」という文字列に設定します。

ソース

C#

Posted by hidepon