입력 처리하기: 마우스와 키보드 이벤트

입력 처리 클릭과 키보드로 창 반응시키기

입력 처리는 “메시지를 받아 상태를 바꾸고, 다시 그린다”는 흐름으로 동작합니다. 이 구조를 이해하면 클릭이나 키 입력에 따라 화면이 변하는 프로그램을 만들 수 있습니다.
Hello World까지 출력했다면 이런 생각이 들 수 있습니다. “이 창은 왜 아무 반응이 없을까?” 실제 프로그램이라면 클릭하거나 키를 눌렀을 때 화면이 바뀌어야 자연스럽게 느껴집니다. 이번 단계에서는 WinAPI 창이 사용자 입력에 반응하고, 그 결과를 화면에 반영하는 흐름까지 이어서 만들어보겠습니다.

Hello World 다음에는 왜 입력 처리를 배워야 할까?

WinAPI 프로그램은 입력을 처리해야 비로소 “동작하는 프로그램”처럼 보입니다.
이전 단계에서는 WM_PAINT 메시지를 통해 텍스트를 화면에 출력했습니다. 하지만 이 상태는 단순히 보여주기만 하는 정적인 화면입니다. 사용자의 행동에 따라 변화가 있어야 실제 프로그램처럼 느껴집니다.

핵심 흐름은 다음과 같습니다.

  • 입력 → 상태 변경 → 화면 다시 그리기

이 세 단계가 연결되면서 프로그램이 실제로 반응하기 시작합니다.

마우스 클릭 처리하기: WM_LBUTTONDOWN 이해하기

마우스를 클릭하면 WM_LBUTTONDOWN 메시지가 발생합니다. 이 메시지를 통해 클릭 위치와 이벤트를 처리할 수 있습니다.

case WM_LBUTTONDOWN:
{
    int x = LOWORD(lParam);
    int y = HIWORD(lParam);

    g_text = L"마우스를 클릭했습니다";
    InvalidateRect(hwnd, NULL, TRUE);
}
return 0;

lParam에는 클릭 좌표가 들어 있습니다. LOWORD와 HIWORD를 이용하면 X, Y 값을 분리할 수 있습니다.
중요한 점은 단순히 메시지를 처리하는 것이 아니라, 상태값을 바꾸고 화면 갱신까지 연결해야 한다는 것입니다.

키보드 입력 처리하기: WM_KEYDOWN 이해하기

키보드 입력은 WM_KEYDOWN 메시지로 처리합니다. 단, 창이 포커스를 가지고 있어야 메시지가 들어옵니다.

case WM_KEYDOWN:
{
    if (wParam == 'A')
    {
        g_text = L"A 키를 눌렀습니다";
        InvalidateRect(hwnd, NULL, TRUE);
    }
}
return 0;

wParam에는 눌린 키 코드가 들어 있습니다. 이를 통해 특정 키 입력에 따라 동작을 다르게 만들 수 있습니다.
여기서 자주 발생하는 실수는 창에 포커스가 없는 상태에서 키 입력을 테스트하는 것입니다. 반드시 창을 한 번 클릭한 후 입력을 확인해야 합니다.

입력 결과를 화면에 반영하는 흐름 만들기

입력 처리의 핵심은 “값 변경과 화면 갱신은 별개”라는 점입니다.

다음 흐름을 반드시 기억해야 합니다.

  • 입력 메시지 수신
  • 상태값 변경 (예: g_text)
  • InvalidateRect 호출
  • WM_PAINT에서 다시 그림
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    TextOut(hdc, 100, 100, g_text.c_str(), g_text.length());

    EndPaint(hwnd, &ps);
}
return 0;

이 구조를 통해 클릭이나 키 입력에 따라 화면 텍스트가 바뀌는 프로그램을 만들 수 있습니다.
많은 초보자가 “값은 바뀌었는데 화면이 그대로인” 문제를 겪습니다. 대부분 InvalidateRect 호출이 빠진 경우입니다.
이 흐름을 이해하면 WinAPI에서 입력과 화면 출력이 어떻게 연결되는지 명확해집니다. 이제 단순 출력 프로그램을 넘어, 사용자와 상호작용하는 기본 형태를 갖춘 상태입니다.

WinAPI 입문기 하나하나 배워보자

WinAPI 첫 걸음: Hello World 윈도우 만들기

WinAPI 프로그램에서 윈도우를 만들려면 “클래스 등록 → 윈도우 생성 → 메시지 처리”라는 흐름을 반드시 따라야 합니다. 이 구조만 이해하면 처음 보는 코드도 훨씬 쉽게 읽히게 됩니다.
처음 Visual Studio에서 WinAPI 프로젝트를 실행해 보면, 창이 보이지 않거나 잠깐 나타났다 바로 종료되는 경우를 자주 겪게 됩니다. 콘솔 프로그램과 달리 구조가 눈에 보이지 않기 때문입니다. 이 글에서는 실제로 “Hello World”가 화면에 출력되는 윈도우를 단계별로 만들어 보겠습니다.

WinAPI 프로그램의 기본 구조 이해하기

WinAPI 프로그램은 main이 아닌 WinMain에서 시작됩니다. 그리고 일반적인 함수 호출 흐름이 아니라, “메시지 기반 구조”로 동작합니다.

핵심 흐름은 다음 세 단계로 이어집니다.

  • 윈도우 클래스 등록 (설계도 정의)
  • 윈도우 생성 (실제 창 생성)
  • 메시지 처리 (동작 정의)

이 구조를 이해하면 코드가 왜 복잡해 보이는지도 자연스럽게 납득할 수 있습니다. 특히 클래스를 등록하지 않으면 윈도우를 생성할 수 없습니다.

STEP 1 – 윈도우 클래스 등록하기

윈도우를 만들기 위해서는 먼저 WNDCLASS 구조체를 통해 “창의 성격”을 정의해야 합니다.

핵심 요소는 다음과 같습니다.

  1. lpfnWndProc: 메시지를 처리할 함수
  2. hInstance: 프로그램 인스턴스
  3. lpszClassName: 클래스 이름
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"HelloWindow";

RegisterClass(&wc);

여기서 L 접두어는 UNICODE 문자열을 의미합니다. WinAPI는 기본적으로 유니코드를 사용하기 때문에 반드시 붙여주는 것이 안전합니다.
자주 발생하는 실수는 RegisterClass 실패를 확인하지 않는 것입니다. 이 경우 이후 단계도 모두 실패하게 됩니다.

STEP 2 – 실제 윈도우 생성 및 표시

클래스를 등록했다면 이제 실제 창을 생성합니다.

HWND hwnd = CreateWindow(
    L"HelloWindow",
    L"Hello World Window",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    500, 300,
    NULL, NULL, hInstance, NULL
);

ShowWindow(hwnd, nCmdShow);

CreateWindow는 창의 크기, 위치, 스타일을 결정합니다. 그리고 ShowWindow를 호출해야 화면에 표시됩니다.
이 단계에서 가장 흔한 실수는 ShowWindow를 빠뜨리는 것입니다. 실행은 되지만 창이 보이지 않는 원인이 됩니다.

STEP 3 – 메시지 처리와 Hello World 출력

이제 창은 만들어졌지만, 아직 내용은 비어 있습니다. 화면에 무엇을 그릴지는 직접 정의해야 합니다.

WndProc 함수에서 WM_PAINT 메시지를 처리하면 됩니다.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        TextOut(hdc, 100, 100, L"Hello World", 11);

        EndPaint(hwnd, &ps);
    }
    return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

WM_PAINT는 화면을 다시 그릴 때 호출되는 메시지입니다. 이 안에서 TextOut을 사용하면 문자열을 출력할 수 있습니다.

마지막으로 메시지 루프가 필요합니다.

  • 메시지를 받아야 창이 유지됩니다
  • 루프가 없으면 프로그램이 바로 종료됩니다
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

이 구조까지 완성하면 WinAPI의 기본 흐름이 모두 연결됩니다.
윈도우 생성 → 메시지 처리 → 화면 출력까지 이어지며, 정상적인 GUI 프로그램이 동작하게 됩니다.