[C#] 59. 윈도우 폼(Window form)에서 컨트럴(Control)의 이벤트 설정하는 방법


Study/C#  2021. 11. 2. 21:15

안녕하세요. 명월입니다.


이 글은 C#의 윈도우 폼(Window form)에서 컨트럴(Control)의 이벤트 설정하는 방법에 대한 글입니다.


이전 글에서 윈도우 폼에 컨트럴을 추가하고 Control 클래스를 상속받아서 컨트럴을 만드는 방법에 대해서 설명했습니다.

link - [C#] 58. 원도우 폼(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#] 24. 이벤트(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 키워드의 함수를 실행하고 다시 돌아와서 재정의한 OnClick 함수의 스택에서 종료가 됩니다.


만약에 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)에서 컨트럴(Control)의 이벤트 설정하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.