[C#] 60. ウィンドウフォーム(Window form)のイベント設定する方法


Study / C#    作成日付 : 2021/11/02 21:18:08   修正日付 : 2021/11/02 21:18:08

こんにちは。明月です。


この投稿はC#のウィンドウフォーム(Window form)のイベント設定する方法に関する説明です。


以前の投稿でウィンドウフォームにコントルールを追加してControlクラスを継承してコントロールを作成する方法に関して説明しました。

link - [C#] 59. ウィンドウフォーム(Window form)にコントロール(Control)を使い方法


コントロールというのはフォームで動的に動いているオブジェクトという意味ですね。ユーザから入力を受け取るか(TextBox)、マウスからクリックのアクションを受け取るか(Button)、データを表示する(GridView)などの様々なコントロールがあります。

このコントロールの状態が更新する時に発生する処理をイベントと言います。


基本的にコントロールのイベントを受け取る方法が二つの方法があります。

始めはオブジェクトの再定義(override)する方法があります。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // ボタンクラスを継承
  class CustomButton: Button
  {
    // Clickイベント関数を再定義
    protected override void OnClick(EventArgs e)
    {
      // 親クラスのClick処理をする。 (event関数実行)
      base.OnClick(e);
      // コンソールに出力
      Console.WriteLine("Click!!!");
    }
  }
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // メンバー変数
    private CustomButton button = null;
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンクラスのインスタンス生成
      this.button = new CustomButton();
      // 位置設定
      this.button.Location = new System.Drawing.Point(27, 40);
      // コントロールの名設定
      this.button.Name = "Button";
      // コントロールのサイズ設定
      this.button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      this.button.TabIndex = 0;
      // タブのText設定
      this.button.Text = "Button";
      // フォームにControl追加
      this.Controls.Add(this.button);
    }
  }
}


ボタンをクリックするとコンソールにClickの値が出力されます。

でも、上みたいにイベントを設定することになったら基本的に提供するコントロールを使うためにはすべてのコントロールクラスを継承して再定義しなければならないですね。


二つ目はeventキーワードを利用するイベント設定です。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // ボタンクラスを継承
  class CustomButton: Button
  {
    
  }
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // メンバー変数
    private CustomButton button = null;
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンクラスのインスタンス生成
      this.button = new CustomButton();
      // 位置設定
      this.button.Location = new System.Drawing.Point(27, 40);
      // コントロールの名設定
      this.button.Name = "Button";
      // コントロールのサイズ設定
      this.button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      this.button.TabIndex = 0;
      // タブのText設定
      this.button.Text = "Button";
      // フォームにControl追加
      this.Controls.Add(this.button);
      // クリックイベント
      this.button.Click += Button_Click;
    }
    // クリックイベントのデリゲート関数
    private void Button_Click(object sender, EventArgs e)
    {
      // コンソールに出力
      Console.WriteLine("Button Click!");
    }
  }
}


eventを利用してクリックイベントを追加する方法です。eventキーワードに関しては以前の投稿で説明したことがあるのでご参考してください。

link - [C#] 25. イベント(event)キーワードを使い方


二つの方法ではどの方法が良いかというと仕様別で差異があります。

一応、overrideとeventキーワードを利用するイベントでどの流れで動いているかを説明します。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // ボタンクラスを継承
  class CustomButton: Button
  {
    // Clickイベント関数を再定義
    protected override void OnClick(EventArgs e)
    {
      // コンソールに出力
      Console.WriteLine("Debugger 1 ");
      // 親クラスのClick処理をする。 (event関数実行)
      base.OnClick(e);
      // コンソールに出力
      Console.WriteLine("Debugger 3 ");
    }
  }
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // 初期化
    private CustomButton button = null;
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンクラスのインスタンス生成
      this.button = new CustomButton();
      // 位置設定
      this.button.Location = new System.Drawing.Point(27, 40);
      // コントロールの名設定
      this.button.Name = "Button";
      // コントロールのサイズ設定
      this.button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      this.button.TabIndex = 0;
      // タブのText設定
      this.button.Text = "Button";
      // フォームにControl追加
      this.Controls.Add(this.button);
      // クリックイベント
      this.button.Click += Button_Click;
    }
    // クリックイベントのデリゲート関数
    private void Button_Click(object sender, EventArgs e)
    {
      // コンソールに出力
      Console.WriteLine("Debugger 2");
    }
  }
}


実行順番を見ると再定義したOnClick関数が呼び出されます。その後でeventキーワードのイベント関数が呼び出されます。そしてまた、OnClick関数のコンソールに出力が実行します。

つまり、外部クラスのイベントを発生する前には再定義したOnClick関数が呼び出されるし、base.OnClickを通って外部eventキーワードの関数を実行して、再定義した関数に戻ってスタックが終了します。


もし、base.OnClick関数をコメントしたらどうでしょう?外部に登録したeventキーワードの関数が実行されません。

そしてbase.OnClickの呼び出すところでEventArgsクラスで情報を 클래스로 정보를 やり取りしますが、パラメータの値を再定義すれば?OnClickのデータとeventキーワードから発生した関数の間でデータをやり取りすることができることです。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // デコレーターパターンのCustromEventArgsクラス
  class CustromEventArgs : EventArgs
  {
    // メンバー変数
    private EventArgs args;
    // コンストラクタ
    public CustromEventArgs(EventArgs args)
    {
      this.args = args;
    }
    // プロパティ追加
    public string Data { get; set; }
  }
  // ボタンクラスを継承
  class CustomButton1 : Button
  {
    // Clickイベント関数を再定義
    protected override void OnClick(EventArgs e)
    {
      // パラメータeの再生成
      var args = new CustromEventArgs(e);
      // Data値を設定
      args.Data = "CustomButton1";
      // 親クラスのClick処理をする。 (event関数実行)
      base.OnClick(args);
    }
  }
  // ボタンクラスを継承
  class CustomButton2 : Button
  {
    // Clickイベント関数を再定義
    protected override void OnClick(EventArgs e)
    {
      // パラメータeの再生成
      var args = new CustromEventArgs(e);
      // Data値を設定
      args.Data = "CustomButton2";
      // 親クラスのClick処理をする。 (event関数実行)
      base.OnClick(args);
    }
  }
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // メンバー変数のボタン
    private Button button1 = null;
    private Button button2 = null;
    // Buttonクラスの初期設定関数
    private Button setInitButton(Button button, string name, int point)
    {
      // 位置設定
      button.Location = new System.Drawing.Point(27, point);
      // コントロールの名設定
      button.Name = name;
      // コントロールのサイズ設定
      button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      button.TabIndex = 0;
      // タブのText設定
      button.Text = "Button";
      // リターン
      return button;
    }
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンインスタンス生成後、初期設定
      this.button1 = setInitButton(new CustomButton1(), "Button1", 40);
      this.button2 = setInitButton(new CustomButton2(), "Button2", 70);
      // フォームにControl追加
      this.Controls.Add(button1);
      this.Controls.Add(button2);
      // イベント追加(デリゲートタイプが同じなので同じ関数を追加しても良い)
      this.button1.Click += Button_Click;
      this.button2.Click += Button_Click;
    }
    // クリックイベントのデリゲート関数
    private void Button_Click(object sender, EventArgs e)
    {
      // キャスト
      CustromEventArgs args = e as CustromEventArgs;
      // コンソールに出力
      Console.WriteLine(args.Data);
    }
  }
}


上の例をみれとbutton1はCustomButton1クラスのインスタンスを生成するし、button2はCustomButton2クラスのインスタンスを生成します。

そしてClickイベントを追加する時には同じデリゲートタイプなので同じ関数(Button_Click)を設定することが可能です。


また、実行して上のボタンをクリックするとCustomButton1の値を出力するし、下のボタンをクリックするとCustomButton2の値をコンソールに出力することを確認できます。

つまり、再定義関数(override)とイベントのデリゲート関数間にデータを上の例みたいに設定することができます。


でも、デリゲート関数をみるとobject senderがあります。

これが何かというとクリックしたインスタンスオブジェクトです。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // メンバー変数のボタン
    private Button button1 = null;
    private Button button2 = null;
    // Buttonクラスの初期設定関数
    private Button setInitButton(Button button, string name, int point)
    {
      // 位置設定
      button.Location = new System.Drawing.Point(27, point);
      // コントロールの名設定
      button.Name = name;
      // コントロールのサイズ設定
      button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      button.TabIndex = 0;
      // タブのText設定
      button.Text = "Button";
      // リターン
      return button;
    }
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンインスタンス生成後、初期設定
      this.button1 = setInitButton(new Button(), "Button1", 40);
      this.button2 = setInitButton(new Button(), "Button2", 70);
      // フォームにControl追加
      this.Controls.Add(button1);
      this.Controls.Add(button2);
      // イベント追加(デリゲートタイプが同じなので同じ関数を追加しても良い)
      this.button1.Click += Button_Click;
      this.button2.Click += Button_Click;
    }
    // クリックイベントのデリゲート関数
    private void Button_Click(object sender, EventArgs e)
    {
      // キャスト
      var button = sender as Button;
      // コンソールに出力
      Console.WriteLine(button.Name);
    }
  }
}


つまり、別にEventArgsのクラスを再生成して値をやり取りする必要なくて、Buttonのクラスを継承すれば継承したButtonにやり取りデータを含める方が実装しやすいです。


そして、イベントデリゲート関数はラムダ式の関数で切り替えることが可能です。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // Formクラスを継承
  public partial class Form1 : Form
  {
    // メンバー変数ボタン
    private Button button1 = null;
    // Buttonクラスの初期設定関数
    private Button setInitButton(Button button, string name, int point)
    {
      // 位置設定
      button.Location = new System.Drawing.Point(27, point);
      // コントロールの名設定
      button.Name = name;
      // コントロールのサイズ設定
      button.Size = new System.Drawing.Size(75, 23);
      // タブのIndex設定
      button.TabIndex = 0;
      // タブのText設定
      button.Text = "Button";
      // リターン
      return button;
    }
    // コンストラクタ
    public Form1()
    {
      // 初期化
      InitializeComponent();
      // ボタンインスタンス生成後、初期設定
      this.button1 = setInitButton(new Button(), "Button1", 40);
      // フォームにControl追加
      this.Controls.Add(button1);
      // イベント追加(ラムダ式で追加)
      this.button1.Click += (sender, e) =>
      {
        // コンソールに出力
        Console.WriteLine("Click!!!");
      };
    }
  }
}


改めて要約すると一般的にイベントはクラス外部ではeventキーワードを利用してイベント追加、クラス内部ではoverrideの再定義を利用してイベントを追加します。

overrideで再定義した関数にはbase.OnClick(e);の前後のステップで外部イベントとの実行順番を設定することができます。

パラメータは基本的にobjectタイプとEventArgsタイプ、あるいはEventArgsを継承したタイプですが、objectタイプはイベントの主体になるインスタンスの値、つまり、ButtonクリックならButtonのインスタンス値があります。

様々で再使用が多いイベントならデリゲート関数で作成するべきですが、一回性ならラムダ式で作成することがコーディングルールです。


ここまでC#のウィンドウフォーム(Window form)のイベント設定する方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿