본문으로 건너뛰기
Previous
Next
Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드

Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드

Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드

이 글의 핵심

Windows API의 기본 컨트롤은 CreateWindow만으로 생성할 수 있습니다. 버튼·에디트·리스트박스 등 미리 정의된 윈도우 클래스를 사용하며, WM_COMMAND 메시지로 이벤트를 처리합니다. 컨트롤 스타일과 메시지 처리를 이해하면 복잡한 GUI도 쉽게 구현할 수 있습니다.

들어가며

Windows 애플리케이션 개발 초기에는 MFC 없이 순수 Win32 API로 작성된 코드가 주류였습니다. 2000년대 중반까지도 많은 엔터프라이즈 애플리케이션이 Win32 API로 작성되었습니다. 처음엔 복잡해 보이지만, 컨트롤의 본질은 “미리 만들어진 자식 윈도우”라는 것을 이해하면 모든 것이 명확해집니다.

// 버튼도 윈도우, 에디트도 윈도우, 모두 CreateWindow로 생성!
HWND hButton = CreateWindow(L"BUTTON", L"클릭", WS_CHILD | WS_VISIBLE, ...);
HWND hEdit = CreateWindow(L"EDIT", L"", WS_CHILD | WS_VISIBLE, ...);

Windows는 버튼, 에디트, 리스트박스 같은 컨트롤을 미리 정의된 윈도우 클래스로 제공합니다. 우리는 이 클래스 이름으로 CreateWindow를 호출하기만 하면 됩니다.

왜 Win32 컨트롤을 배워야 할까?

실무에서 마주치는 상황들:

  1. 레거시 코드 분석 - 오래된 Windows 앱은 대부분 Win32 API 기반
  2. 커스텀 컨트롤 개발 - 서브클래싱으로 기존 컨트롤 확장
  3. 성능 최적화 - MFC/Qt 오버헤드 없는 가벼운 UI
  4. 다른 프레임워크 이해 - 모든 Windows UI는 Win32 API 위에 구축

2000년대에는 임베디드 시스템이나 IoT 디바이스 설정 도구 개발 시, 30MB가 넘는 MFC 애플리케이션을 순수 Win32 API로 재작성해 2MB 수준으로 줄이는 최적화 작업이 흔했습니다.


1. Windows 컨트롤의 기본 개념

1.1 컨트롤이란?

// 컨트롤 = 미리 정의된 윈도우 클래스를 사용하는 자식 윈도우

// 일반 윈도우 생성 (우리가 WNDCLASS 등록)
RegisterClass(&wc);
HWND hwnd = CreateWindow(L"MyWindowClass", ...);

// 컨트롤 생성 (Windows가 이미 등록한 클래스 사용)
HWND hButton = CreateWindow(L"BUTTON", ...);  // Windows가 제공
HWND hEdit = CreateWindow(L"EDIT", ...);      // Windows가 제공

1.2 미리 정의된 윈도우 클래스

Windows가 제공하는 기본 컨트롤 클래스:

// 1. 버튼 계열
"BUTTON"       // 버튼, 체크박스, 라디오 버튼

// 2. 편집 계열
"EDIT"         // 텍스트 입력 상자

// 3. 선택 계열
"LISTBOX"      // 리스트박스
"COMBOBOX"     // 콤보박스 (드롭다운)

// 4. 정적 컨트롤
"STATIC"       // 텍스트 레이블, 이미지

// 5. 스크롤바
"SCROLLBAR"    // 스크롤바

// 6. 고급 컨트롤 (Common Controls)
WC_LISTVIEW    // 리스트뷰
WC_TREEVIEW    // 트리뷰
WC_TABCONTROL  // 탭 컨트롤
WC_TRACKBAR    // 슬라이더
// ... 등등

2. 버튼 (BUTTON) 완벽 정복

2.1 기본 버튼 생성

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 기본 푸시 버튼
    HWND hButton = CreateWindow(
        L"BUTTON",              // 윈도우 클래스
        L"클릭하세요!",         // 버튼 텍스트
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,  // 스타일
        10,                     // X 위치
        10,                     // Y 위치
        120,                    // 너비
        30,                     // 높이
        hwnd,                   // 부모 윈도우
        (HMENU)ID_BUTTON,       // 컨트롤 ID (또는 메뉴 핸들)
        pCS->hInstance,         // 인스턴스
        NULL                    // 추가 파라미터
    );
    
    return 0;
}

// 버튼 클릭 처리
case WM_COMMAND: {
    int id = LOWORD(wParam);      // 컨트롤 ID
    int event = HIWORD(wParam);   // 알림 코드
    
    if (id == ID_BUTTON && event == BN_CLICKED) {
        MessageBox(hwnd, L"버튼이 클릭되었습니다!", L"알림", MB_OK);
    }
    return 0;
}

2.2 버튼 스타일

// 1. 기본 버튼
BS_PUSHBUTTON       // 일반 푸시 버튼
BS_DEFPUSHBUTTON    // 기본 버튼 (굵은 테두리, Enter 키로 활성화)

// 2. 체크박스
BS_CHECKBOX         // 일반 체크박스
BS_AUTOCHECKBOX     // 자동 체크/언체크
BS_3STATE           // 3가지 상태 (체크/언체크/그레이)
BS_AUTO3STATE       // 자동 3-state

// 3. 라디오 버튼
BS_RADIOBUTTON      // 일반 라디오 버튼
BS_AUTORADIOBUTTON  // 자동 선택/해제

// 4. 외형 스타일
BS_FLAT             // 평면 버튼
BS_BITMAP           // 비트맵 버튼
BS_ICON             // 아이콘 버튼
BS_OWNERDRAW        // 오너 드로우 (직접 그리기)

2.3 체크박스 예제

#define ID_CHECK1  1001
#define ID_CHECK2  1002

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 자동 체크박스
    CreateWindow(
        L"BUTTON", L"자동 저장",
        WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
        10, 10, 120, 20,
        hwnd, (HMENU)ID_CHECK1, pCS->hInstance, NULL
    );
    
    CreateWindow(
        L"BUTTON", L"알림 표시",
        WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
        10, 40, 120, 20,
        hwnd, (HMENU)ID_CHECK2, pCS->hInstance, NULL
    );
    
    // 확인 버튼
    CreateWindow(
        L"BUTTON", L"확인",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        10, 70, 80, 25,
        hwnd, (HMENU)ID_OK, pCS->hInstance, NULL
    );
    
    return 0;
}

case WM_COMMAND: {
    int id = LOWORD(wParam);
    
    if (id == ID_OK && HIWORD(wParam) == BN_CLICKED) {
        // 체크 상태 확인
        BOOL autoSave = IsDlgButtonChecked(hwnd, ID_CHECK1);
        BOOL showNotif = IsDlgButtonChecked(hwnd, ID_CHECK2);
        
        wchar_t buf[256];
        swprintf_s(buf, L"자동 저장: %s\n알림 표시: %s",
            autoSave ? L"ON" : L"OFF",
            showNotif ? L"ON" : L"OFF"
        );
        MessageBox(hwnd, buf, L"설정", MB_OK);
    }
    return 0;
}

2.4 라디오 버튼 예제 (그룹)

#define ID_RADIO1  2001
#define ID_RADIO2  2002
#define ID_RADIO3  2003

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 라디오 버튼 그룹 (첫 번째는 WS_GROUP 필수!)
    CreateWindow(
        L"BUTTON", L"빨강",
        WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
        10, 10, 80, 20,
        hwnd, (HMENU)ID_RADIO1, pCS->hInstance, NULL
    );
    
    CreateWindow(
        L"BUTTON", L"초록",
        WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
        10, 35, 80, 20,
        hwnd, (HMENU)ID_RADIO2, pCS->hInstance, NULL
    );
    
    CreateWindow(
        L"BUTTON", L"파랑",
        WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
        10, 60, 80, 20,
        hwnd, (HMENU)ID_RADIO3, pCS->hInstance, NULL
    );
    
    // 첫 번째 라디오 버튼 기본 선택
    CheckRadioButton(hwnd, ID_RADIO1, ID_RADIO3, ID_RADIO1);
    
    return 0;
}

case WM_COMMAND: {
    int id = LOWORD(wParam);
    
    if (id >= ID_RADIO1 && id <= ID_RADIO3 && HIWORD(wParam) == BN_CLICKED) {
        // 선택된 라디오 버튼 확인
        const wchar_t* color = nullptr;
        if (IsDlgButtonChecked(hwnd, ID_RADIO1)) color = L"빨강";
        else if (IsDlgButtonChecked(hwnd, ID_RADIO2)) color = L"초록";
        else if (IsDlgButtonChecked(hwnd, ID_RADIO3)) color = L"파랑";
        
        if (color) {
            wchar_t buf[128];
            swprintf_s(buf, L"선택된 색상: %s", color);
            SetWindowText(hwnd, buf);
        }
    }
    return 0;
}

2.5 버튼 제어 함수

// 체크 상태 설정/확인
CheckDlgButton(hwnd, ID_CHECK, BST_CHECKED);    // 체크
CheckDlgButton(hwnd, ID_CHECK, BST_UNCHECKED);  // 언체크
UINT state = IsDlgButtonChecked(hwnd, ID_CHECK);

// 라디오 버튼 그룹 설정
CheckRadioButton(hwnd, ID_RADIO1, ID_RADIO3, ID_RADIO2);  // ID_RADIO2 선택

// 버튼 활성화/비활성화
EnableWindow(GetDlgItem(hwnd, ID_BUTTON), FALSE);  // 비활성화
EnableWindow(GetDlgItem(hwnd, ID_BUTTON), TRUE);   // 활성화

// 버튼 텍스트 변경
SetDlgItemText(hwnd, ID_BUTTON, L"새 텍스트");

// 버튼 클릭 시뮬레이션
SendMessage(GetDlgItem(hwnd, ID_BUTTON), BM_CLICK, 0, 0);

3. 에디트 컨트롤 (EDIT) 완벽 정복

3.1 기본 에디트 컨트롤

#define ID_EDIT  3001

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 단일 라인 에디트
    HWND hEdit = CreateWindowEx(
        WS_EX_CLIENTEDGE,       // 3D 테두리
        L"EDIT",
        L"",                    // 초기 텍스트
        WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
        10, 10, 300, 25,
        hwnd, (HMENU)ID_EDIT, pCS->hInstance, NULL
    );
    
    // 폰트 설정 (기본 폰트는 작음)
    HFONT hFont = CreateFont(
        16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"맑은 고딕"
    );
    SendMessage(hEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
    
    return 0;
}

3.2 에디트 스타일

// 수평 정렬
ES_LEFT         // 왼쪽 정렬 (기본값)
ES_CENTER       // 가운데 정렬
ES_RIGHT        // 오른쪽 정렬

// 스크롤
ES_AUTOHSCROLL  // 자동 가로 스크롤
ES_AUTOVSCROLL  // 자동 세로 스크롤

// 여러 줄
ES_MULTILINE    // 여러 줄 입력 가능

// 읽기 전용
ES_READONLY     // 읽기만 가능

// 비밀번호
ES_PASSWORD     // 입력 문자를 '*'로 표시

// 숫자만
ES_NUMBER       // 숫자만 입력 가능

// 대소문자
ES_LOWERCASE    // 모두 소문자로
ES_UPPERCASE    // 모두 대문자로

3.3 여러 줄 에디트 (메모장 스타일)

#define ID_EDIT_MULTI  3002

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 여러 줄 에디트 (스크롤바 포함)
    HWND hEdit = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_BORDER | 
        ES_MULTILINE |              // 여러 줄
        ES_AUTOVSCROLL |            // 세로 스크롤
        WS_VSCROLL |                // 스크롤바 표시
        ES_WANTRETURN,              // Enter 키 줄바꿈
        10, 10, 400, 300,
        hwnd, (HMENU)ID_EDIT_MULTI, pCS->hInstance, NULL
    );
    
    // 초기 텍스트 설정
    SetWindowText(hEdit, L"여러 줄을\n입력할 수 있습니다.\n\n세 번째 줄입니다.");
    
    return 0;
}

3.4 에디트 컨트롤 제어

// 텍스트 가져오기
wchar_t buf[256];
GetDlgItemText(hwnd, ID_EDIT, buf, 256);

// 또는
HWND hEdit = GetDlgItem(hwnd, ID_EDIT);
int len = GetWindowTextLength(hEdit);
wchar_t* text = new wchar_t[len + 1];
GetWindowText(hEdit, text, len + 1);
// ... 사용
delete[] text;

// 텍스트 설정
SetDlgItemText(hwnd, ID_EDIT, L"새 텍스트");

// 정수 값 설정/가져오기
SetDlgItemInt(hwnd, ID_EDIT, 12345, FALSE);  // FALSE = unsigned
int value = GetDlgItemInt(hwnd, ID_EDIT, NULL, FALSE);

// 선택 영역 설정 (커서 위치)
SendMessage(hEdit, EM_SETSEL, 0, -1);  // 전체 선택
SendMessage(hEdit, EM_SETSEL, 5, 10);  // 5~10번째 문자 선택

// 읽기 전용 설정
SendMessage(hEdit, EM_SETREADONLY, TRUE, 0);

// 글자 수 제한
SendMessage(hEdit, EM_SETLIMITTEXT, 100, 0);  // 최대 100자

// 수정 여부 확인
BOOL modified = SendMessage(hEdit, EM_GETMODIFY, 0, 0);
SendMessage(hEdit, EM_SETMODIFY, FALSE, 0);  // 수정 플래그 초기화

3.5 에디트 알림 메시지

case WM_COMMAND: {
    int id = LOWORD(wParam);
    int event = HIWORD(wParam);
    
    if (id == ID_EDIT) {
        switch (event) {
            case EN_CHANGE:
                // 텍스트가 변경됨
                break;
                
            case EN_SETFOCUS:
                // 포커스를 받음
                break;
                
            case EN_KILLFOCUS:
                // 포커스를 잃음
                break;
                
            case EN_MAXTEXT:
                // 최대 글자 수 도달
                MessageBox(hwnd, L"최대 글자 수에 도달했습니다!", L"경고", MB_OK);
                break;
        }
    }
    return 0;
}

3.6 실전 예제: 입력 검증

#define ID_EDIT_NAME   3001
#define ID_EDIT_AGE    3002
#define ID_BUTTON_OK   3003

case WM_COMMAND: {
    int id = LOWORD(wParam);
    int event = HIWORD(wParam);
    
    // 나이 입력란: 숫자만 입력
    if (id == ID_EDIT_AGE && event == EN_CHANGE) {
        wchar_t buf[10];
        GetDlgItemText(hwnd, ID_EDIT_AGE, buf, 10);
        
        // 숫자가 아닌 문자 제거
        std::wstring text = buf;
        text.erase(
            std::remove_if(text.begin(), text.end(),
                [](wchar_t c) { return !iswdigit(c); }),
            text.end()
        );
        
        if (text != buf) {
            SetDlgItemText(hwnd, ID_EDIT_AGE, text.c_str());
            // 커서를 끝으로 이동
            HWND hEdit = GetDlgItem(hwnd, ID_EDIT_AGE);
            int len = (int)text.length();
            SendMessage(hEdit, EM_SETSEL, len, len);
        }
    }
    
    // 확인 버튼
    if (id == ID_BUTTON_OK && event == BN_CLICKED) {
        wchar_t name[256], age[10];
        GetDlgItemText(hwnd, ID_EDIT_NAME, name, 256);
        GetDlgItemText(hwnd, ID_EDIT_AGE, age, 10);
        
        if (wcslen(name) == 0) {
            MessageBox(hwnd, L"이름을 입력하세요!", L"오류", MB_ICONERROR);
            SetFocus(GetDlgItem(hwnd, ID_EDIT_NAME));
            return 0;
        }
        
        if (wcslen(age) == 0) {
            MessageBox(hwnd, L"나이를 입력하세요!", L"오류", MB_ICONERROR);
            SetFocus(GetDlgItem(hwnd, ID_EDIT_AGE));
            return 0;
        }
        
        wchar_t buf[512];
        swprintf_s(buf, L"이름: %s\n나이: %s세", name, age);
        MessageBox(hwnd, buf, L"입력 완료", MB_OK);
    }
    return 0;
}

4. 리스트박스 (LISTBOX) 완벽 정복

4.1 기본 리스트박스

#define ID_LISTBOX  4001

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 리스트박스 생성
    HWND hList = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"LISTBOX", NULL,
        WS_CHILD | WS_VISIBLE | WS_BORDER | 
        WS_VSCROLL |            // 세로 스크롤바
        LBS_NOTIFY,             // 알림 메시지 전송
        10, 10, 200, 150,
        hwnd, (HMENU)ID_LISTBOX, pCS->hInstance, NULL
    );
    
    // 항목 추가
    SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)L"항목 1");
    SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)L"항목 2");
    SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)L"항목 3");
    
    // 첫 번째 항목 선택
    SendMessage(hList, LB_SETCURSEL, 0, 0);
    
    return 0;
}

4.2 리스트박스 스타일

// 선택 방식
LBS_NOTIFY          // 알림 메시지 전송
LBS_MULTIPLESEL     // 여러 항목 선택 (Ctrl+클릭)
LBS_EXTENDEDSEL     // 확장 선택 (Shift+클릭)

// 정렬
LBS_SORT            // 자동 정렬

// 스크롤
LBS_DISABLENOSCROLL // 항목이 적어도 스크롤바 표시 (비활성화)

// 다중 컬럼
LBS_MULTICOLUMN     // 여러 컬럼 (가로 스크롤)

// 소유자 그리기
LBS_OWNERDRAWFIXED  // 오너 드로우 (고정 높이)
LBS_OWNERDRAWVARIABLE // 오너 드로우 (가변 높이)

4.3 리스트박스 제어 함수

HWND hList = GetDlgItem(hwnd, ID_LISTBOX);

// 항목 추가
int index = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)L"새 항목");

// 특정 위치에 삽입
SendMessage(hList, LB_INSERTSTRING, 0, (LPARAM)L"맨 앞 항목");

// 항목 삭제
SendMessage(hList, LB_DELETESTRING, index, 0);

// 모든 항목 삭제
SendMessage(hList, LB_RESETCONTENT, 0, 0);

// 항목 개수
int count = SendMessage(hList, LB_GETCOUNT, 0, 0);

// 선택된 항목 인덱스
int sel = SendMessage(hList, LB_GETCURSEL, 0, 0);
if (sel == LB_ERR) {
    // 선택된 항목 없음
}

// 선택된 항목 텍스트
wchar_t buf[256];
SendMessage(hList, LB_GETTEXT, sel, (LPARAM)buf);

// 항목 선택
SendMessage(hList, LB_SETCURSEL, index, 0);

// 항목 검색
int found = SendMessage(hList, LB_FINDSTRING, -1, (LPARAM)L"검색어");
if (found != LB_ERR) {
    SendMessage(hList, LB_SETCURSEL, found, 0);
}

4.4 리스트박스 알림 메시지

case WM_COMMAND: {
    int id = LOWORD(wParam);
    int event = HIWORD(wParam);
    
    if (id == ID_LISTBOX) {
        switch (event) {
            case LBN_SELCHANGE: {
                // 선택 변경
                HWND hList = GetDlgItem(hwnd, ID_LISTBOX);
                int sel = SendMessage(hList, LB_GETCURSEL, 0, 0);
                
                if (sel != LB_ERR) {
                    wchar_t buf[256];
                    SendMessage(hList, LB_GETTEXT, sel, (LPARAM)buf);
                    
                    wchar_t msg[512];
                    swprintf_s(msg, L"선택: %s (인덱스: %d)", buf, sel);
                    SetWindowText(hwnd, msg);
                }
                break;
            }
            
            case LBN_DBLCLK: {
                // 더블클릭
                HWND hList = GetDlgItem(hwnd, ID_LISTBOX);
                int sel = SendMessage(hList, LB_GETCURSEL, 0, 0);
                
                if (sel != LB_ERR) {
                    wchar_t buf[256];
                    SendMessage(hList, LB_GETTEXT, sel, (LPARAM)buf);
                    MessageBox(hwnd, buf, L"더블클릭", MB_OK);
                }
                break;
            }
        }
    }
    return 0;
}

4.5 실전 예제: 항목 관리

#define ID_LISTBOX      4001
#define ID_EDIT_ITEM    4002
#define ID_BTN_ADD      4003
#define ID_BTN_DELETE   4004
#define ID_BTN_CLEAR    4005

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 리스트박스
    CreateWindowEx(WS_EX_CLIENTEDGE, L"LISTBOX", NULL,
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOTIFY,
        10, 10, 200, 200,
        hwnd, (HMENU)ID_LISTBOX, pCS->hInstance, NULL);
    
    // 에디트 (항목 입력)
    CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
        220, 10, 150, 25,
        hwnd, (HMENU)ID_EDIT_ITEM, pCS->hInstance, NULL);
    
    // 버튼들
    CreateWindow(L"BUTTON", L"추가",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        380, 10, 80, 25,
        hwnd, (HMENU)ID_BTN_ADD, pCS->hInstance, NULL);
    
    CreateWindow(L"BUTTON", L"삭제",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        220, 45, 80, 25,
        hwnd, (HMENU)ID_BTN_DELETE, pCS->hInstance, NULL);
    
    CreateWindow(L"BUTTON", L"전체 삭제",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        310, 45, 80, 25,
        hwnd, (HMENU)ID_BTN_CLEAR, pCS->hInstance, NULL);
    
    return 0;
}

case WM_COMMAND: {
    int id = LOWORD(wParam);
    int event = HIWORD(wParam);
    HWND hList = GetDlgItem(hwnd, ID_LISTBOX);
    
    // 추가 버튼
    if (id == ID_BTN_ADD && event == BN_CLICKED) {
        wchar_t buf[256];
        GetDlgItemText(hwnd, ID_EDIT_ITEM, buf, 256);
        
        if (wcslen(buf) > 0) {
            SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buf);
            SetDlgItemText(hwnd, ID_EDIT_ITEM, L"");  // 입력란 비우기
            SetFocus(GetDlgItem(hwnd, ID_EDIT_ITEM));
        }
    }
    
    // 삭제 버튼
    if (id == ID_BTN_DELETE && event == BN_CLICKED) {
        int sel = SendMessage(hList, LB_GETCURSEL, 0, 0);
        if (sel != LB_ERR) {
            SendMessage(hList, LB_DELETESTRING, sel, 0);
            
            // 다음 항목 선택
            int count = SendMessage(hList, LB_GETCOUNT, 0, 0);
            if (count > 0) {
                if (sel >= count) sel = count - 1;
                SendMessage(hList, LB_SETCURSEL, sel, 0);
            }
        }
    }
    
    // 전체 삭제 버튼
    if (id == ID_BTN_CLEAR && event == BN_CLICKED) {
        if (MessageBox(hwnd, L"모든 항목을 삭제하시겠습니까?", L"확인",
                       MB_YESNO | MB_ICONQUESTION) == IDYES) {
            SendMessage(hList, LB_RESETCONTENT, 0, 0);
        }
    }
    
    // Enter 키로 추가
    if (id == ID_EDIT_ITEM && event == EN_CHANGE) {
        // 입력란에서 Enter 키 처리는 별도로 필요
    }
    
    return 0;
}

// Enter 키 처리
case WM_KEYDOWN:
    if (wParam == VK_RETURN) {
        HWND hFocus = GetFocus();
        if (hFocus == GetDlgItem(hwnd, ID_EDIT_ITEM)) {
            // 추가 버튼 클릭 시뮬레이션
            SendMessage(hwnd, WM_COMMAND, 
                MAKEWPARAM(ID_BTN_ADD, BN_CLICKED),
                (LPARAM)GetDlgItem(hwnd, ID_BTN_ADD));
        }
    }
    break;

5. 콤보박스 (COMBOBOX) 완벽 정복

5.1 기본 콤보박스

#define ID_COMBOBOX  5001

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 드롭다운 콤보박스
    HWND hCombo = CreateWindow(
        L"COMBOBOX", NULL,
        WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
        10, 10, 200, 200,  // 높이는 드롭다운 크기 (충분히 크게)
        hwnd, (HMENU)ID_COMBOBOX, pCS->hInstance, NULL
    );
    
    // 항목 추가
    SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"서울");
    SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"부산");
    SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"대구");
    SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"인천");
    SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"광주");
    
    // 첫 번째 항목 선택
    SendMessage(hCombo, CB_SETCURSEL, 0, 0);
    
    return 0;
}

5.2 콤보박스 스타일

// 타입
CBS_SIMPLE          // 리스트박스 항상 표시
CBS_DROPDOWN        // 드롭다운 (편집 가능)
CBS_DROPDOWNLIST    // 드롭다운 (편집 불가, 선택만)

// 기타
CBS_SORT            // 자동 정렬
CBS_AUTOHSCROLL     // 가로 스크롤
CBS_OWNERDRAWFIXED  // 오너 드로우

5.3 콤보박스 제어 함수

HWND hCombo = GetDlgItem(hwnd, ID_COMBOBOX);

// 항목 추가
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"새 항목");

// 특정 위치에 삽입
SendMessage(hCombo, CB_INSERTSTRING, 0, (LPARAM)L"맨 앞 항목");

// 항목 삭제
SendMessage(hCombo, CB_DELETESTRING, index, 0);

// 모든 항목 삭제
SendMessage(hCombo, CB_RESETCONTENT, 0, 0);

// 항목 개수
int count = SendMessage(hCombo, CB_GETCOUNT, 0, 0);

// 선택된 항목 인덱스
int sel = SendMessage(hCombo, CB_GETCURSEL, 0, 0);

// 선택된 항목 텍스트
wchar_t buf[256];
SendMessage(hCombo, CB_GETLBTEXT, sel, (LPARAM)buf);

// 항목 선택
SendMessage(hCombo, CB_SETCURSEL, index, 0);

// 에디트 영역 텍스트 (CBS_DROPDOWN만)
GetWindowText(hCombo, buf, 256);
SetWindowText(hCombo, L"새 텍스트");

5.4 콤보박스 알림 메시지

case WM_COMMAND: {
    int id = LOWORD(wParam);
    int event = HIWORD(wParam);
    
    if (id == ID_COMBOBOX) {
        switch (event) {
            case CBN_SELCHANGE: {
                // 선택 변경
                HWND hCombo = GetDlgItem(hwnd, ID_COMBOBOX);
                int sel = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
                
                if (sel != CB_ERR) {
                    wchar_t buf[256];
                    SendMessage(hCombo, CB_GETLBTEXT, sel, (LPARAM)buf);
                    
                    wchar_t msg[512];
                    swprintf_s(msg, L"선택: %s", buf);
                    MessageBox(hwnd, msg, L"알림", MB_OK);
                }
                break;
            }
            
            case CBN_DROPDOWN:
                // 드롭다운 열림
                break;
                
            case CBN_CLOSEUP:
                // 드롭다운 닫힘
                break;
                
            case CBN_EDITCHANGE:
                // 에디트 영역 텍스트 변경 (CBS_DROPDOWN만)
                break;
        }
    }
    return 0;
}

6. 정적 컨트롤 (STATIC) - 레이블

6.1 텍스트 레이블

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 일반 텍스트
    CreateWindow(
        L"STATIC", L"이름:",
        WS_CHILD | WS_VISIBLE | SS_LEFT,
        10, 10, 100, 20,
        hwnd, NULL, pCS->hInstance, NULL
    );
    
    // 굵은 테두리
    CreateWindow(
        L"STATIC", L"중요 알림!",
        WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,
        10, 40, 200, 30,
        hwnd, NULL, pCS->hInstance, NULL
    );
    
    return 0;
}

6.2 이미지 표시

#define ID_STATIC_ICON  6001

case WM_CREATE: {
    CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
    
    // 아이콘 표시
    HWND hStatic = CreateWindow(
        L"STATIC", NULL,
        WS_CHILD | WS_VISIBLE | SS_ICON,
        10, 10, 64, 64,
        hwnd, (HMENU)ID_STATIC_ICON, pCS->hInstance, NULL
    );
    
    // 아이콘 로드 및 설정
    HICON hIcon = LoadIcon(NULL, IDI_INFORMATION);
    SendMessage(hStatic, STM_SETICON, (WPARAM)hIcon, 0);
    
    return 0;
}

7. 실전 종합 예제: 사용자 등록 폼

#include <windows.h>
#include <string>
#include <vector>

#define ID_EDIT_NAME      1001
#define ID_EDIT_EMAIL     1002
#define ID_EDIT_AGE       1003
#define ID_COMBO_CITY     1004
#define ID_CHECK_AGREE    1005
#define ID_RADIO_MALE     1006
#define ID_RADIO_FEMALE   1007
#define ID_BTN_REGISTER   1008
#define ID_LISTBOX_USERS  1009

struct User {
    std::wstring name;
    std::wstring email;
    int age;
    std::wstring city;
    std::wstring gender;
};

std::vector<User> g_users;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    WNDCLASSEX wc = {};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = L"UserRegistrationApp";
    
    RegisterClassEx(&wc);
    
    HWND hwnd = CreateWindow(
        L"UserRegistrationApp",
        L"사용자 등록 시스템",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 700, 500,
        NULL, NULL, hInstance, NULL
    );
    
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE: {
            CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
            int x = 20, y = 20;
            
            // 레이블 + 에디트 (이름)
            CreateWindow(L"STATIC", L"이름:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                x, y, 80, 20, hwnd, NULL, pCS->hInstance, NULL);
            CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
                WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
                x + 100, y, 200, 25, hwnd, (HMENU)ID_EDIT_NAME, pCS->hInstance, NULL);
            y += 35;
            
            // 이메일
            CreateWindow(L"STATIC", L"이메일:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                x, y, 80, 20, hwnd, NULL, pCS->hInstance, NULL);
            CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
                WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,
                x + 100, y, 200, 25, hwnd, (HMENU)ID_EDIT_EMAIL, pCS->hInstance, NULL);
            y += 35;
            
            // 나이
            CreateWindow(L"STATIC", L"나이:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                x, y, 80, 20, hwnd, NULL, pCS->hInstance, NULL);
            CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
                WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER,
                x + 100, y, 100, 25, hwnd, (HMENU)ID_EDIT_AGE, pCS->hInstance, NULL);
            y += 35;
            
            // 도시 (콤보박스)
            CreateWindow(L"STATIC", L"도시:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                x, y, 80, 20, hwnd, NULL, pCS->hInstance, NULL);
            HWND hCombo = CreateWindow(L"COMBOBOX", NULL,
                WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
                x + 100, y, 200, 200, hwnd, (HMENU)ID_COMBO_CITY, pCS->hInstance, NULL);
            
            SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"서울");
            SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"부산");
            SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"대구");
            SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"인천");
            SendMessage(hCombo, CB_SETCURSEL, 0, 0);
            y += 35;
            
            // 성별 (라디오 버튼)
            CreateWindow(L"STATIC", L"성별:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                x, y, 80, 20, hwnd, NULL, pCS->hInstance, NULL);
            CreateWindow(L"BUTTON", L"남성",
                WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP,
                x + 100, y, 80, 20, hwnd, (HMENU)ID_RADIO_MALE, pCS->hInstance, NULL);
            CreateWindow(L"BUTTON", L"여성",
                WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
                x + 190, y, 80, 20, hwnd, (HMENU)ID_RADIO_FEMALE, pCS->hInstance, NULL);
            CheckRadioButton(hwnd, ID_RADIO_MALE, ID_RADIO_FEMALE, ID_RADIO_MALE);
            y += 35;
            
            // 약관 동의 (체크박스)
            CreateWindow(L"BUTTON", L"개인정보 수집 및 이용에 동의합니다",
                WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
                x, y, 300, 20, hwnd, (HMENU)ID_CHECK_AGREE, pCS->hInstance, NULL);
            y += 35;
            
            // 등록 버튼
            CreateWindow(L"BUTTON", L"등록",
                WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
                x, y, 100, 30, hwnd, (HMENU)ID_BTN_REGISTER, pCS->hInstance, NULL);
            
            // 등록된 사용자 목록 (리스트박스)
            CreateWindow(L"STATIC", L"등록된 사용자:",
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                350, 20, 120, 20, hwnd, NULL, pCS->hInstance, NULL);
            CreateWindowEx(WS_EX_CLIENTEDGE, L"LISTBOX", NULL,
                WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOTIFY,
                350, 45, 300, 350, hwnd, (HMENU)ID_LISTBOX_USERS, pCS->hInstance, NULL);
            
            return 0;
        }
        
        case WM_COMMAND: {
            int id = LOWORD(wParam);
            int event = HIWORD(wParam);
            
            if (id == ID_BTN_REGISTER && event == BN_CLICKED) {
                // 입력 검증
                wchar_t name[256], email[256], age[10], city[256];
                GetDlgItemText(hwnd, ID_EDIT_NAME, name, 256);
                GetDlgItemText(hwnd, ID_EDIT_EMAIL, email, 256);
                GetDlgItemText(hwnd, ID_EDIT_AGE, age, 10);
                
                HWND hCombo = GetDlgItem(hwnd, ID_COMBO_CITY);
                int cityIdx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
                SendMessage(hCombo, CB_GETLBTEXT, cityIdx, (LPARAM)city);
                
                if (wcslen(name) == 0) {
                    MessageBox(hwnd, L"이름을 입력하세요!", L"오류", MB_ICONERROR);
                    return 0;
                }
                
                if (wcslen(email) == 0) {
                    MessageBox(hwnd, L"이메일을 입력하세요!", L"오류", MB_ICONERROR);
                    return 0;
                }
                
                if (wcslen(age) == 0) {
                    MessageBox(hwnd, L"나이를 입력하세요!", L"오류", MB_ICONERROR);
                    return 0;
                }
                
                if (!IsDlgButtonChecked(hwnd, ID_CHECK_AGREE)) {
                    MessageBox(hwnd, L"약관에 동의해주세요!", L"오류", MB_ICONERROR);
                    return 0;
                }
                
                // 사용자 정보 저장
                User user;
                user.name = name;
                user.email = email;
                user.age = _wtoi(age);
                user.city = city;
                user.gender = IsDlgButtonChecked(hwnd, ID_RADIO_MALE) ? L"남성" : L"여성";
                g_users.push_back(user);
                
                // 리스트박스에 추가
                wchar_t buf[512];
                swprintf_s(buf, L"%s (%s, %d세, %s)",
                    user.name.c_str(), user.gender.c_str(), user.age, user.city.c_str());
                SendMessage(GetDlgItem(hwnd, ID_LISTBOX_USERS), LB_ADDSTRING, 0, (LPARAM)buf);
                
                // 입력란 초기화
                SetDlgItemText(hwnd, ID_EDIT_NAME, L"");
                SetDlgItemText(hwnd, ID_EDIT_EMAIL, L"");
                SetDlgItemText(hwnd, ID_EDIT_AGE, L"");
                CheckDlgButton(hwnd, ID_CHECK_AGREE, BST_UNCHECKED);
                
                MessageBox(hwnd, L"등록되었습니다!", L"성공", MB_ICONINFORMATION);
                SetFocus(GetDlgItem(hwnd, ID_EDIT_NAME));
            }
            
            return 0;
        }
        
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글이 도움이 되셨나요? Windows API 컨트롤 사용법을 마스터하는 데 도움이 되었기를 바랍니다!

다음 글에서는 Windows API GDI - 그래픽 그리기를 다루겠습니다. 🎨