Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-17 00:00
관리 메뉴

nomad-programmer

[Programming/C#] WinForm : Application 클래스 본문

Programming/C#

[Programming/C#] WinForm : Application 클래스

scii 2020. 10. 2. 02:51

WinForm은 폼 디자이너라는 툴을 제공해서 프로그래머가 그림 그리듯 UI를 만들 수 있게 한다. 이른바 WYSIWYG(What You See Is What You Get) 방식의 개발을 지원하는 것이다.

컨트롤을 윈도우 위에 배치할 때마다 폼 디자이너는 프로그램의 UI를 표시하는 한편, 뒤로는 관련 C# 코드를 자동으로 만들어 준다. 프로퍼티를 변경할 때, 이벤트 처리기를 추가할 때도 자동으로 코드를 수정해준다.

C# 코드로 WinForm 윈도우 만들기

Win32 API를 이용하여 윈도우를 만드는 절차

  1. 윈도우 클래스를 정의한다(윈도우에 대한 정보를 가지고 있는 구조체).
  2. 정의된 윈도우 클래스를 등록한다.
  3. 윈도우를 생성한다.
  4. 윈도우를 사용자에게 보여준다.
  5. 메시지 루프를 돌면서 프로그램을 시작한다.

WinForm클래스를 이용하여 윈도우를 만드는 절차

  1. System.Windows.Forms.Form 클래스에서 파생된 윈도우 폼 클래스를 선언한다.
  2. 1번에서 만든 클래스의 인스턴스를 System.Windows.Forms.Application.Run() 메소드에 매개 변수로 넘겨 호출한다.

Win32API에 비해 절차도 훨씬 간편하고 실제 코드의 양도 1/10 정도로 WinForm 클래스를 사용하는 코드가 더 적다.

using System;

namespace SimpleWindow
{
    // MainApp이 System.Windows.Forms.From 클래스로부터 상속받도록 선언
    internal class MainApp : System.Windows.Forms.Form
    {
        static int Main(string[] args)
        {
            // Application.Run() 메소드에 MainApp의 인스턴스를 매개 변수로 넘겨 호출
            System.Windows.Forms.Application.Run(new MainApp());

            return 0;
        }
    }
}

Application 클래스

Application 클래스는 크게 두 가지 역할을 수행한다.

  1. 윈도우 응용 프로그램을 시작하고 종료시키는 메소드를 제공한다.
  2. 윈도우 메시지를 처리한다.
class MyForm : System.Windows.Forms.Form { }

class MainApp
{
    static void Main(string[] args)
    {
        MyForm form = new MyForm();
        
        // Form 클래스는 여러 가지 이벤트를 정의하고 있는데,
        // 그 중 Click 이벤트는 윈도우를 클릭했을 때 발생하는 이벤트이다.
        // 따라서 이 코도는 윈도우를 클릭하면 Application.Exit()를 호출한다.
        form.Click += new EventHandler(
            (sender, eventArgs) =>
            {
                Application.Exit();
            });
        
        Application.Run(form)
    }
}

Exit() 메소드에 대해 필히 알아둬야 하는것이 있다. Exit() 메소드가 호출된다고 해서 응용 프로그램이 바로 종료되는 것은 아니다. 이 메소드가 하는 일은 응용 프로그램이 갖고 있는 모든 윈도우를 닫은 뒤 Run() 메소드가 반환되도록 하는 것이다. 따라서 Run() 메소드 뒤에 자원을 정리하는 코드를 넣어두면 응용 프로그램을 안전하게 종료시킬 수 있다.

using System;
using System.Windows.Forms;

namespace SimpleWindow
{
    internal class MainApp : Form
    {
        static int Main(string[] args)
        {
            MainApp form = new MainApp();

            form.Click += new EventHandler(
                (sender, eventArgs) =>
                {
                    Console.WriteLine("Closing Window...");
                    Application.Exit();
                });

            Console.WriteLine("Starting Window Application...");
            Application.Run(form);

            Console.WriteLine("Exiting Window Application...");

            return 0;
        }
    }
}

Application의 "메시지 필터링 (Message Filtering)"

Application 클래스는 응용 프로그램이 받고 있는 수많은 메시지 중에 관심 있는 메시지만 걸러낼 수 있는 메시지 필터링 기능을 갖고 있다. 가령 만든 응용 프로그램을 사용자가 Alt+F4 키를 눌러 종료시키는 것을 막고 싶다면, 바로 이 기능을 이용해서 해당 키 입력 메시지를 걸려내면 응용 프로그램의 윈도우가 닫히는 것을 막을 수 있다.

윈도우 운영체제에서 정의하고 있는 메시지는 식별 번호(ID)가 붙여져 있다. 예들 들어 WM_LBUTTONDOWN 메시지는 ID가 0x201로 정의되어 있다. Application 클래스는 특정 ID를 갖는 메시지를 걸러내는 필터를 함께 등록해뒀다가 응용 프로그램에 메시지가 전달되면 해당 필터를 동작시킨다.
만약 메시지의 ID가 필터에서 관심을 갖고 있는 값이라면 필터는 메시지를 요리하고,그렇지 않다면 메시지를 거르지 않고 메시지를 받아야 하는 폼이나 컨트롤로 보내서 이벤트를 발생시킨다.

메시지 발생 --> 메시지 필터 --> 이벤트 발생(펄터에서 걸러지지 않은 메시지는 이벤트를 발생시킴) --> 이벤트 처리기

Application.AddMessageFilter() 메소드는 응용 프로그램에 메시지 필터를 설치한다. 이 메소드는 매개 변수로 IMessageFilter 인터페이스를 구현하는 파생 클래스의 인스턴스를 매개 변수로 받으며, IMessageFilter는 다음과 같이 PreFilterMessage() 메소드를 구현할 것을 요구한다.

public interface IMessageFilter
{
    bool PreFilterMessage(ref Message m);
}

IMessageFilter 인터페이스의 구현 예

public class MessageFilter : IMessagegFilter
{
    public bool PreFilterMessage(ref Message m)
    {
        // 마우스 이동부터 마우스의 왼쪽, 오른쪽, 가운데 버튼 동작,
        // 마우스 휠 조작 메시지를 모두 걸러낸다.
        if (m.Msg >= 0x200 && m.Msg <= 0x20E)
        {
            Console.WriteLine("발생한 메시지: " + m.Msg);
            return true;
        }
        return false;
    }
}

PreFilterMessage()를 구현할 때는 입력받은 메시지를 처리했으니 응용 프로그램은 관심을 가질 필요가 없다는 true를 반환하거나, 메시지를 건드리지 않았으니 응용 프로그램더러 처리해야 한다고 false를 반환하면 된다. 그리고 매개 변수로 받아들이는 Message 구조체는 다음과 같은 프로퍼티를 갖고 있는데, 이 중 Msg 프로퍼티는 메시지의 ID를 담고 있다.

프로퍼티 설명
HWnd 메시지를 받는 윈도우의 핸들(Handle)이다. 윈도우의 인스턴스를 식별하고 관리하기 위해 운영체제가 붙여놓은 번호가 바로 핸들이다.
Msg 메시지 ID이다.
LParam 메시지를 처리하는 데 필요한 정보가 담겨 있다.
WParam 메시지를 처리하는 데 필요한 부가 정보가 담겨 있다.
Result 메시지 처리에 대한 응답으로 윈도우 운영체제에 반환되는 값을 지정한다.

메시지 필터를 구현했다면 다음과 같이 AddMessageFilter() 메소드를 호출하여 등록하면 된다.

Application.AddMessageFilter(new MessageFilter());

다음은 메시지 필터 예제 프로그램이다. 이 프로그램은 응용 프로그램이 윈도우로부터 전달받는 모든 메시지를 출력한다. 단, WM_PAINT(0x0F), WM_MOUSEMOVE(0x200), WM_TIMER(0x113) 메시지는 제외.

using System;
using System.Windows.Forms;

namespace SimpleWindow
{
    class MessageFilter: IMessageFilter
    {
        public bool PreFilterMessage(ref Message msg)
        {
            if(msg.Msg == 0x0F || msg.Msg == 0xA0 ||
                msg.Msg == 0x200 || msg.Msg == 0x113)
            {
                return false;
            }
            Console.WriteLine($"{msg.ToString()}: {msg.Msg}");

            if(msg.Msg == 0x201)
            {
                Application.Exit();
            }
            return true;
        }
    }

    internal class MainApp : Form
    {
        static int Main(string[] args)
        {
            Application.AddMessageFilter(new MessageFilter());
            Application.Run(new MainApp());

            return 0;
        }
    }
}


/* 결과

msg=0x31f hwnd=0x6051c wparam=0x1 lparam=0x0 result=0x0: 799
msg=0xc256 hwnd=0x6051c wparam=0x0 lparam=0x0 result=0x0: 49750
msg=0xc0e3 hwnd=0x6051c wparam=0x0 lparam=0x0 result=0x0: 49379
msg=0x7fff (WM_REFLECT + ???) hwnd=0x700cc wparam=0x1 lparam=0x0 result=0x0: 32767
msg=0x7fff (WM_REFLECT + ???) hwnd=0x700cc wparam=0x2 lparam=0x0 result=0x0: 32767
msg=0x7fff (WM_REFLECT + ???) hwnd=0x700cc wparam=0x3 lparam=0x0 result=0x0: 32767
msg=0x60 hwnd=0x901da wparam=0x6 lparam=0x0 result=0x0: 96

...

*/
Comments