Windows API 다이얼로그 | 대화상자와 공용 컨트롤 완벽 가이드
이 글의 핵심
다이얼로그는 사용자 입력을 받는 팝업 창입니다. 리소스 에디터로 UI를 디자인하고 DialogBox로 모달, CreateDialog로 비모달 다이얼로그를 생성합니다. ListView, TreeView 같은 공용 컨트롤로 복잡한 데이터를 표시할 수 있습니다.
들어가며
Windows 다이얼로그는 1980년대 중반부터 Windows UI의 핵심 요소였습니다. 설정 창, 파일 열기, 메시지 박스 등 대부분의 팝업 창이 다이얼로그로 구현됩니다. 리소스 에디터(Visual Studio)를 사용하면 코드 없이 UI를 시각적으로 디자인할 수 있습니다.
// 가장 간단한 다이얼로그
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
// 호출
DialogBox(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, DlgProc);
왜 다이얼로그를 사용할까?
다이얼로그의 장점:
- 빠른 UI 개발 - 리소스 에디터로 드래그 앤 드롭
- 자동 레이아웃 - 탭 순서, 키보드 네비게이션 자동 처리
- 표준 UI - Windows 표준 다이얼로그 스타일
- 재사용성 - 리소스를 여러 곳에서 재사용 가능
1. 다이얼로그 기본
1.1 리소스 스크립트 (RC 파일)
// resource.h
#define IDD_DIALOG1 101
#define IDC_EDIT1 1001
#define IDC_BUTTON1 1002
#define IDOK 1
#define IDCANCEL 2
// dialog.rc
#include "resource.h"
IDD_DIALOG1 DIALOGEX 0, 0, 300, 200
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "내 다이얼로그"
FONT 9, "맑은 고딕"
BEGIN
LTEXT "이름:",IDC_STATIC,10,10,40,14
EDITTEXT IDC_EDIT1,60,10,200,14,ES_AUTOHSCROLL
DEFPUSHBUTTON "확인",IDOK,100,180,50,14
PUSHBUTTON "취소",IDCANCEL,160,180,50,14
END
1.2 모달 다이얼로그 (Modal)
#include <windows.h>
#include "resource.h"
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_INITDIALOG:
// 다이얼로그 초기화
SetDlgItemText(hDlg, IDC_EDIT1, L"기본값");
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK: {
// 확인 버튼
wchar_t buf[256];
GetDlgItemText(hDlg, IDC_EDIT1, buf, 256);
MessageBox(hDlg, buf, L"입력한 값", MB_OK);
EndDialog(hDlg, IDOK);
return TRUE;
}
case IDCANCEL:
// 취소 버튼
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
break;
case WM_CLOSE:
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
return FALSE; // ★ 처리 안 함 (DefDlgProc 호출 안 함!)
}
// 호출 (부모 윈도우에서)
case WM_COMMAND:
if (LOWORD(wParam) == ID_FILE_OPEN) {
INT_PTR result = DialogBox(
hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
hwnd, // 부모 윈도우
DlgProc
);
if (result == IDOK) {
MessageBox(hwnd, L"확인 눌렀음", L"알림", MB_OK);
}
}
break;
1.3 비모달 다이얼로그 (Modeless)
HWND g_hModelessDlg = NULL;
INT_PTR CALLBACK ModelessDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDCANCEL) {
DestroyWindow(hDlg); // ★ EndDialog 아님!
g_hModelessDlg = NULL;
return TRUE;
}
break;
case WM_DESTROY:
g_hModelessDlg = NULL;
return TRUE;
}
return FALSE;
}
// 생성
case WM_COMMAND:
if (LOWORD(wParam) == ID_VIEW_TOOLBAR) {
if (g_hModelessDlg == NULL) {
g_hModelessDlg = CreateDialog(
hInstance,
MAKEINTRESOURCE(IDD_TOOLBAR),
hwnd,
ModelessDlgProc
);
ShowWindow(g_hModelessDlg, SW_SHOW);
} else {
SetFocus(g_hModelessDlg);
}
}
break;
// ★ 메시지 루프에서 처리 필요!
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
// 비모달 다이얼로그 메시지 처리
if (g_hModelessDlg == NULL || !IsDialogMessage(g_hModelessDlg, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
1.4 다이얼로그 데이터 전달
// 데이터 구조체
struct DialogData {
wchar_t name[256];
int age;
};
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
static DialogData* pData = nullptr;
switch (msg) {
case WM_INITDIALOG:
// lParam으로 데이터 받기
pData = (DialogData*)lParam;
// 초기값 설정
SetDlgItemText(hDlg, IDC_EDIT_NAME, pData->name);
SetDlgItemInt(hDlg, IDC_EDIT_AGE, pData->age, FALSE);
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK) {
// 데이터 저장
GetDlgItemText(hDlg, IDC_EDIT_NAME, pData->name, 256);
pData->age = GetDlgItemInt(hDlg, IDC_EDIT_AGE, NULL, FALSE);
EndDialog(hDlg, IDOK);
return TRUE;
}
break;
}
return FALSE;
}
// 호출
DialogData data = {};
wcscpy_s(data.name, L"홍길동");
data.age = 30;
INT_PTR result = DialogBoxParam(
hInstance,
MAKEINTRESOURCE(IDD_USERINFO),
hwnd,
DlgProc,
(LPARAM)&data // ★ 데이터 전달
);
if (result == IDOK) {
wchar_t buf[512];
swprintf_s(buf, L"이름: %s\n나이: %d", data.name, data.age);
MessageBox(hwnd, buf, L"결과", MB_OK);
}
2. 공용 컨트롤 초기화
2.1 InitCommonControlsEx
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
// 공용 컨트롤 초기화
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LISTVIEW_CLASSES | // ListView
ICC_TREEVIEW_CLASSES | // TreeView
ICC_BAR_CLASSES | // Toolbar, StatusBar
ICC_TAB_CLASSES | // TabControl
ICC_PROGRESS_CLASS | // ProgressBar
ICC_UPDOWN_CLASS; // Spin Control
if (!InitCommonControlsEx(&icex)) {
MessageBox(NULL, L"공용 컨트롤 초기화 실패!", L"오류", MB_ICONERROR);
return 1;
}
// ... 윈도우 생성
}
3. ListView 컨트롤
3.1 기본 ListView 생성
#define ID_LISTVIEW 2001
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// ListView 생성
HWND hListView = CreateWindowEx(
WS_EX_CLIENTEDGE,
WC_LISTVIEW,
L"",
WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS,
10, 10, 500, 300,
hwnd, (HMENU)ID_LISTVIEW, pCS->hInstance, NULL
);
// 확장 스타일 (전체 행 선택, 그리드)
ListView_SetExtendedListViewStyle(hListView,
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
// 컬럼 추가
LVCOLUMN lvc = {};
lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lvc.pszText = (LPWSTR)L"이름";
lvc.cx = 150;
lvc.iSubItem = 0;
ListView_InsertColumn(hListView, 0, &lvc);
lvc.pszText = (LPWSTR)L"나이";
lvc.cx = 80;
lvc.iSubItem = 1;
ListView_InsertColumn(hListView, 1, &lvc);
lvc.pszText = (LPWSTR)L"직업";
lvc.cx = 150;
lvc.iSubItem = 2;
ListView_InsertColumn(hListView, 2, &lvc);
// 항목 추가
LVITEM lvi = {};
lvi.mask = LVIF_TEXT;
lvi.iItem = 0;
lvi.iSubItem = 0;
lvi.pszText = (LPWSTR)L"홍길동";
ListView_InsertItem(hListView, &lvi);
ListView_SetItemText(hListView, 0, 1, (LPWSTR)L"30");
ListView_SetItemText(hListView, 0, 2, (LPWSTR)L"개발자");
lvi.iItem = 1;
lvi.pszText = (LPWSTR)L"김철수";
ListView_InsertItem(hListView, &lvi);
ListView_SetItemText(hListView, 1, 1, (LPWSTR)L"25");
ListView_SetItemText(hListView, 1, 2, (LPWSTR)L"디자이너");
return 0;
}
3.2 ListView 이벤트 처리
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->idFrom == ID_LISTVIEW) {
switch (pNMHDR->code) {
case NM_CLICK: {
// 클릭
NMITEMACTIVATE* pNMIA = (NMITEMACTIVATE*)lParam;
wchar_t buf[256];
ListView_GetItemText(pNMHDR->hwndFrom, pNMIA->iItem, 0, buf, 256);
wchar_t msg[512];
swprintf_s(msg, L"선택: %s (행: %d, 열: %d)",
buf, pNMIA->iItem, pNMIA->iSubItem);
SetWindowText(hwnd, msg);
break;
}
case NM_DBLCLK: {
// 더블클릭
NMITEMACTIVATE* pNMIA = (NMITEMACTIVATE*)lParam;
wchar_t buf[256];
ListView_GetItemText(pNMHDR->hwndFrom, pNMIA->iItem, 0, buf, 256);
MessageBox(hwnd, buf, L"더블클릭", MB_OK);
break;
}
case LVN_ITEMCHANGED: {
// 선택 변경
NMLISTVIEW* pNMLV = (NMLISTVIEW*)lParam;
if (pNMLV->uNewState & LVIS_SELECTED) {
// 선택됨
}
break;
}
case LVN_COLUMNCLICK: {
// 컬럼 헤더 클릭 (정렬 구현)
NMLISTVIEW* pNMLV = (NMLISTVIEW*)lParam;
wchar_t buf[256];
swprintf_s(buf, L"컬럼 %d 클릭 (정렬 구현)", pNMLV->iSubItem);
MessageBox(hwnd, buf, L"알림", MB_OK);
break;
}
}
}
break;
}
3.3 ListView 정렬
// 정렬 콜백
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
// lParam1, lParam2: 각 항목의 lParam 값
// lParamSort: ListView_SortItems의 lParamSort
// 예: 문자열 비교
wchar_t* str1 = (wchar_t*)lParam1;
wchar_t* str2 = (wchar_t*)lParam2;
return wcscmp(str1, str2);
}
// 정렬 실행
ListView_SortItems(hListView, CompareFunc, 0);
4. TreeView 컨트롤
4.1 기본 TreeView 생성
#define ID_TREEVIEW 2002
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// TreeView 생성
HWND hTreeView = CreateWindowEx(
WS_EX_CLIENTEDGE,
WC_TREEVIEW,
L"",
WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT,
10, 10, 300, 400,
hwnd, (HMENU)ID_TREEVIEW, pCS->hInstance, NULL
);
// 루트 항목
TVINSERTSTRUCT tvis = {};
tvis.hParent = TVI_ROOT;
tvis.hInsertAfter = TVI_LAST;
tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
tvis.item.pszText = (LPWSTR)L"내 컴퓨터";
tvis.item.lParam = (LPARAM)0;
HTREEITEM hRoot = TreeView_InsertItem(hTreeView, &tvis);
// 자식 항목들
tvis.hParent = hRoot;
tvis.item.pszText = (LPWSTR)L"C: 드라이브";
HTREEITEM hC = TreeView_InsertItem(hTreeView, &tvis);
tvis.item.pszText = (LPWSTR)L"D: 드라이브";
HTREEITEM hD = TreeView_InsertItem(hTreeView, &tvis);
// C 드라이브 하위 항목
tvis.hParent = hC;
tvis.item.pszText = (LPWSTR)L"Program Files";
TreeView_InsertItem(hTreeView, &tvis);
tvis.item.pszText = (LPWSTR)L"Windows";
TreeView_InsertItem(hTreeView, &tvis);
tvis.item.pszText = (LPWSTR)L"Users";
TreeView_InsertItem(hTreeView, &tvis);
// 루트 확장
TreeView_Expand(hTreeView, hRoot, TVE_EXPAND);
return 0;
}
4.2 TreeView 이벤트 처리
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->idFrom == ID_TREEVIEW) {
switch (pNMHDR->code) {
case TVN_SELCHANGED: {
// 선택 변경
NMTREEVIEW* pNMTV = (NMTREEVIEW*)lParam;
wchar_t buf[256];
TVITEM item = {};
item.mask = TVIF_TEXT;
item.hItem = pNMTV->itemNew.hItem;
item.pszText = buf;
item.cchTextMax = 256;
TreeView_GetItem(pNMHDR->hwndFrom, &item);
wchar_t msg[512];
swprintf_s(msg, L"선택: %s", buf);
SetWindowText(hwnd, msg);
break;
}
case NM_DBLCLK: {
// 더블클릭
HTREEITEM hItem = TreeView_GetSelection(pNMHDR->hwndFrom);
wchar_t buf[256];
TVITEM item = {};
item.mask = TVIF_TEXT;
item.hItem = hItem;
item.pszText = buf;
item.cchTextMax = 256;
TreeView_GetItem(pNMHDR->hwndFrom, &item);
MessageBox(hwnd, buf, L"더블클릭", MB_OK);
break;
}
case TVN_ITEMEXPANDING: {
// 확장/축소 전
NMTREEVIEW* pNMTV = (NMTREEVIEW*)lParam;
if (pNMTV->action == TVE_EXPAND) {
// 확장 중
} else if (pNMTV->action == TVE_COLLAPSE) {
// 축소 중
}
break;
}
}
}
break;
}
5. TabControl 컨트롤
5.1 기본 TabControl 생성
#define ID_TABCONTROL 2003
HWND g_hTab1 = NULL;
HWND g_hTab2 = NULL;
HWND g_hTab3 = NULL;
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// TabControl 생성
HWND hTab = CreateWindow(
WC_TABCONTROL,
L"",
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
10, 10, 500, 400,
hwnd, (HMENU)ID_TABCONTROL, pCS->hInstance, NULL
);
// 탭 추가
TCITEM tie = {};
tie.mask = TCIF_TEXT;
tie.pszText = (LPWSTR)L"일반";
TabCtrl_InsertItem(hTab, 0, &tie);
tie.pszText = (LPWSTR)L"고급";
TabCtrl_InsertItem(hTab, 1, &tie);
tie.pszText = (LPWSTR)L"정보";
TabCtrl_InsertItem(hTab, 2, &tie);
// 각 탭의 클라이언트 영역 계산
RECT rcTab;
GetClientRect(hTab, &rcTab);
TabCtrl_AdjustRect(hTab, FALSE, &rcTab);
// 탭 1 내용 (에디트)
g_hTab1 = CreateWindow(
L"EDIT", L"일반 탭 내용",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL,
rcTab.left, rcTab.top,
rcTab.right - rcTab.left, rcTab.bottom - rcTab.top,
hTab, NULL, pCS->hInstance, NULL
);
// 탭 2 내용 (체크박스들)
g_hTab2 = CreateWindow(
L"BUTTON", L"고급 옵션 1",
WS_CHILD | BS_AUTOCHECKBOX,
rcTab.left + 10, rcTab.top + 10, 200, 20,
hTab, NULL, pCS->hInstance, NULL
);
// 탭 3 내용 (정적 텍스트)
g_hTab3 = CreateWindow(
L"STATIC", L"버전: 1.0\n작성자: pkglog.com",
WS_CHILD,
rcTab.left + 10, rcTab.top + 10, 300, 100,
hTab, NULL, pCS->hInstance, NULL
);
// 첫 번째 탭만 표시
ShowWindow(g_hTab1, SW_SHOW);
ShowWindow(g_hTab2, SW_HIDE);
ShowWindow(g_hTab3, SW_HIDE);
return 0;
}
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->idFrom == ID_TABCONTROL && pNMHDR->code == TCN_SELCHANGE) {
// 탭 변경
int sel = TabCtrl_GetCurSel(pNMHDR->hwndFrom);
// 모두 숨기기
ShowWindow(g_hTab1, SW_HIDE);
ShowWindow(g_hTab2, SW_HIDE);
ShowWindow(g_hTab3, SW_HIDE);
// 선택된 탭만 표시
switch (sel) {
case 0: ShowWindow(g_hTab1, SW_SHOW); break;
case 1: ShowWindow(g_hTab2, SW_SHOW); break;
case 2: ShowWindow(g_hTab3, SW_SHOW); break;
}
}
break;
}
6. ProgressBar와 기타 컨트롤
6.1 ProgressBar
#define ID_PROGRESSBAR 2004
HWND g_hProgress = NULL;
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// ProgressBar 생성
g_hProgress = CreateWindow(
PROGRESS_CLASS,
L"",
WS_CHILD | WS_VISIBLE,
10, 10, 400, 25,
hwnd, (HMENU)ID_PROGRESSBAR, pCS->hInstance, NULL
);
// 범위 설정 (0~100)
SendMessage(g_hProgress, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
SendMessage(g_hProgress, PBM_SETSTEP, 1, 0);
// 시작 버튼
CreateWindow(
L"BUTTON", L"시작",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
10, 50, 100, 30,
hwnd, (HMENU)ID_BTN_START, pCS->hInstance, NULL
);
return 0;
}
case WM_COMMAND:
if (LOWORD(wParam) == ID_BTN_START) {
// 타이머로 진행률 시뮬레이션
SetTimer(hwnd, 1, 100, NULL);
}
break;
case WM_TIMER:
if (wParam == 1) {
int pos = (int)SendMessage(g_hProgress, PBM_GETPOS, 0, 0);
if (pos >= 100) {
KillTimer(hwnd, 1);
SendMessage(g_hProgress, PBM_SETPOS, 0, 0);
MessageBox(hwnd, L"완료!", L"알림", MB_OK);
} else {
SendMessage(g_hProgress, PBM_STEPIT, 0, 0);
}
}
break;
6.2 Slider (TrackBar)
#define ID_SLIDER 2005
#define ID_STATIC_VALUE 2006
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// Slider 생성
HWND hSlider = CreateWindow(
TRACKBAR_CLASS,
L"",
WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS,
10, 10, 400, 40,
hwnd, (HMENU)ID_SLIDER, pCS->hInstance, NULL
);
// 범위 설정 (0~100)
SendMessage(hSlider, TBM_SETRANGE, TRUE, MAKELONG(0, 100));
SendMessage(hSlider, TBM_SETPOS, TRUE, 50);
SendMessage(hSlider, TBM_SETTICFREQ, 10, 0);
// 값 표시 레이블
CreateWindow(
L"STATIC", L"값: 50",
WS_CHILD | WS_VISIBLE,
420, 15, 100, 20,
hwnd, (HMENU)ID_STATIC_VALUE, pCS->hInstance, NULL
);
return 0;
}
case WM_HSCROLL: {
if ((HWND)lParam == GetDlgItem(hwnd, ID_SLIDER)) {
int pos = (int)SendMessage((HWND)lParam, TBM_GETPOS, 0, 0);
wchar_t buf[64];
swprintf_s(buf, L"값: %d", pos);
SetDlgItemText(hwnd, ID_STATIC_VALUE, buf);
}
break;
}
6.3 Spin Control (Up-Down)
#define ID_EDIT_SPIN 2007
#define ID_SPIN 2008
case WM_CREATE: {
CREATESTRUCT* pCS = (CREATESTRUCT*)lParam;
// 에디트
HWND hEdit = CreateWindowEx(
WS_EX_CLIENTEDGE,
L"EDIT", L"50",
WS_CHILD | WS_VISIBLE | ES_RIGHT,
10, 10, 80, 25,
hwnd, (HMENU)ID_EDIT_SPIN, pCS->hInstance, NULL
);
// Spin Control
HWND hSpin = CreateWindow(
UPDOWN_CLASS,
L"",
WS_CHILD | WS_VISIBLE | UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS,
0, 0, 0, 0, // 위치는 자동
hwnd, (HMENU)ID_SPIN, pCS->hInstance, NULL
);
// Buddy 설정
SendMessage(hSpin, UDM_SETBUDDY, (WPARAM)hEdit, 0);
SendMessage(hSpin, UDM_SETRANGE, 0, MAKELONG(100, 0));
SendMessage(hSpin, UDM_SETPOS, 0, 50);
return 0;
}
7. 실전 예제: 설정 다이얼로그
// resource.h
#define IDD_SETTINGS 101
#define IDC_TAB 1001
#define IDC_CHECK_AUTO 1002
#define IDC_CHECK_NOTIFY 1003
#define IDC_SLIDER_VOLUME 1004
#define IDC_STATIC_VOLUME 1005
#define IDC_COMBO_LANG 1006
// settings.rc
IDD_SETTINGS DIALOGEX 0, 0, 400, 300
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "설정"
FONT 9, "맑은 고딕"
BEGIN
CONTROL "",IDC_TAB,"SysTabControl32",0x0,7,7,386,265
DEFPUSHBUTTON "확인",IDOK,240,280,70,14
PUSHBUTTON "취소",IDCANCEL,320,280,70,14
END
// settings.cpp
struct SettingsData {
BOOL autoSave;
BOOL showNotify;
int volume;
int language; // 0=한국어, 1=English
};
SettingsData g_settings = {TRUE, TRUE, 50, 0};
INT_PTR CALLBACK SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hTab = NULL;
static HWND hTabPage1 = NULL;
static HWND hTabPage2 = NULL;
switch (msg) {
case WM_INITDIALOG: {
hTab = GetDlgItem(hDlg, IDC_TAB);
// 탭 추가
TCITEM tie = {};
tie.mask = TCIF_TEXT;
tie.pszText = (LPWSTR)L"일반";
TabCtrl_InsertItem(hTab, 0, &tie);
tie.pszText = (LPWSTR)L"고급";
TabCtrl_InsertItem(hTab, 1, &tie);
// 탭 1 페이지 (일반)
RECT rc;
GetClientRect(hTab, &rc);
TabCtrl_AdjustRect(hTab, FALSE, &rc);
// 체크박스들
CreateWindow(L"BUTTON", L"자동 저장",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
rc.left + 20, rc.top + 20, 200, 20,
hTab, (HMENU)IDC_CHECK_AUTO, GetModuleHandle(NULL), NULL);
CreateWindow(L"BUTTON", L"알림 표시",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
rc.left + 20, rc.top + 50, 200, 20,
hTab, (HMENU)IDC_CHECK_NOTIFY, GetModuleHandle(NULL), NULL);
// 초기값 설정
CheckDlgButton(hTab, IDC_CHECK_AUTO, g_settings.autoSave ? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(hTab, IDC_CHECK_NOTIFY, g_settings.showNotify ? BST_CHECKED : BST_UNCHECKED);
// 볼륨 슬라이더
CreateWindow(L"STATIC", L"볼륨:",
WS_CHILD | WS_VISIBLE,
rc.left + 20, rc.top + 80, 50, 20,
hTab, NULL, GetModuleHandle(NULL), NULL);
HWND hSlider = CreateWindow(TRACKBAR_CLASS, L"",
WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS,
rc.left + 80, rc.top + 80, 200, 40,
hTab, (HMENU)IDC_SLIDER_VOLUME, GetModuleHandle(NULL), NULL);
SendMessage(hSlider, TBM_SETRANGE, TRUE, MAKELONG(0, 100));
SendMessage(hSlider, TBM_SETPOS, TRUE, g_settings.volume);
wchar_t volBuf[32];
swprintf_s(volBuf, L"%d%%", g_settings.volume);
CreateWindow(L"STATIC", volBuf,
WS_CHILD | WS_VISIBLE,
rc.left + 290, rc.top + 85, 50, 20,
hTab, (HMENU)IDC_STATIC_VOLUME, GetModuleHandle(NULL), NULL);
// 언어 콤보박스
CreateWindow(L"STATIC", L"언어:",
WS_CHILD | WS_VISIBLE,
rc.left + 20, rc.top + 130, 50, 20,
hTab, NULL, GetModuleHandle(NULL), NULL);
HWND hCombo = CreateWindow(L"COMBOBOX", L"",
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
rc.left + 80, rc.top + 128, 150, 200,
hTab, (HMENU)IDC_COMBO_LANG, GetModuleHandle(NULL), NULL);
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"한국어");
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"English");
SendMessage(hCombo, CB_SETCURSEL, g_settings.language, 0);
return TRUE;
}
case WM_HSCROLL: {
if ((HWND)lParam == GetDlgItem(hTab, IDC_SLIDER_VOLUME)) {
int pos = (int)SendMessage((HWND)lParam, TBM_GETPOS, 0, 0);
wchar_t buf[32];
swprintf_s(buf, L"%d%%", pos);
SetDlgItemText(hTab, IDC_STATIC_VOLUME, buf);
}
break;
}
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->idFrom == IDC_TAB && pNMHDR->code == TCN_SELCHANGE) {
// 탭 변경 처리
}
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDOK: {
// 설정 저장
g_settings.autoSave = IsDlgButtonChecked(hTab, IDC_CHECK_AUTO);
g_settings.showNotify = IsDlgButtonChecked(hTab, IDC_CHECK_NOTIFY);
g_settings.volume = (int)SendMessage(
GetDlgItem(hTab, IDC_SLIDER_VOLUME), TBM_GETPOS, 0, 0);
g_settings.language = (int)SendMessage(
GetDlgItem(hTab, IDC_COMBO_LANG), CB_GETCURSEL, 0, 0);
EndDialog(hDlg, IDOK);
return TRUE;
}
case IDCANCEL:
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
break;
}
}
return FALSE;
}
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Windows API 기초 | 메시지 루프와 윈도우 프로시저 완벽 가이드
- Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드
- Windows API GDI | 그래픽 그리기 완벽 가이드
이 글이 도움이 되셨나요? Windows 다이얼로그와 공용 컨트롤을 마스터하는 데 도움이 되었기를 바랍니다!
다음 글에서는 Windows API 파일 I/O와 레지스트리를 다루겠습니다. 💾