Win32 - API - Dialog
[TOC]
다이얼로그
- 부모 윈도우와 개별적으로 생성되어 사용자로부터 정보를 입력 받는 창
- 형태
- 모달
- 모달리스
- 모달
모달(Modal)과 모델리스(Modeless) 다이얼로그
모달 다이얼로그
- 다이얼로그가 열린 상태에서는 다이얼로그 이외의 윈도우를 조작할 수 없다
- DialogBox() 함수로 생성하며 다이얼로그가 닫힐 때까지 리턴하지 않는다.
모델리스(Modeless) 다이얼로그
- 다이얼로그가 열려 있어도 다른 윈도우를 조작할 수 있다.
- CreateDialog() 함수로 생성하며 즉시 다이얼로그 핸들을 반환한다.
다이얼로그 생성/종료
- 리소스
- 리소스 추가 > 다이얼로그
// 다이얼로그 생성
/*
HINSTANCE hInstance : 응용프로그램의 인스턴스 핸들러
LPCSTR lpTemplateName : 리소스 아이디 문자열
HWND hWndParent : 다이얼로그의 부모 윈도우 핸들러
DLGPROC lpDialogFunc : 다이얼로그 프로시저
LPARAM dwInitParam : 다이얼로그 초기화 인자값
*/
INT_PTR DialogBox(HINSTANCE hInstance, LPCSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam);
// 예 - 다이얼로그 생성
DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DialogProc);
// 다이얼로그 종료
/*
HWND hDlg : 다이얼로그 윈도우 핸들러
INT_PTR nResult : DialogBox() 함수의 리턴값
*/
BOOL EndDialog(HWND hDlg, INT_PTR nResult);
// 예 - 다이얼로그 종료
EndDialog(hWnd, 0);
다이얼로그 프로시저
- 다이얼로그에서 발생하는 메시지를 처리하기 위한 함수
- 메시지를 제대로 처리했으면 TRUE, 처리하지 못했으면 FALSE를 리턴한다.
- FALSE를 리턴 시 이 메시지의 처리는 OS가 해준다.
- 따라서 DefWindowProc을 호출할 필요가 없다.
- WndProc과 다르게 WM_CREATE 메시지 대신 WM_INITDIALOG 메시지를 받는다.
- 이 메시지를 받고 TRUE를 리턴하면 초기화가 완료된다.
- WM_COMMAND 메시지를 주로 처리
- LOWORD(wParam) : 메시지를 보낸 컨트롤러의 ID
- HIWORD(wParam) : 통지 코드
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return true;
case WM_COMMAND:
switch (wParam)
{
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
return TRUE;
default:
break;
}
default:
return FALSE;
break;
}
return FALSE;
}
다이얼로그와 부모 윈도우의 데이터 전달
- EndDialog()에서 반환값을 넣어준다.
- INT_PTR 값 하나만 전달 가능하므로 보통 호출 결과를 나타낸다.
- 두 개 이상의 값 전달은 보통 전역변수를 사용하여 공유한다.
모달 다이얼로그로 데이터 변경하기
// 다이얼로그 박스 생성
// DialogBox의 반환값을 보고 성공/취소를 판단하여 처리한다.
// 반환값을 EndDialog의 두번째 파라미터로 넘기는 값이 반환된다.
if (DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DialogProc) != 0) {
InvalidateRect(hWnd, NULL, TRUE);
};
// 다이얼로그 프로시저
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
// 다이얼로그 초기화 시 각 컨트롤러에 초기값을 넣어준다.
// IDC_X, IDC_Y, IDC_STR는 Edit 컨트롤
SetDlgItemInt(hWnd, IDC_X, g_nDialogX, FALSE);
SetDlgItemInt(hWnd, IDC_Y, g_nDialogY, FALSE);
SetDlgItemText(hWnd, IDC_STR, g_szDialogStr);
return true;
case WM_COMMAND:
switch (wParam)
{
case IDOK:
// 작업이 끝나고 다이얼로그창을 성공적으로 닫을 때 컨트롤러에 있는 값을 넣어준다.
g_nDialogX = GetDlgItemInt(hWnd, IDC_X, NULL, FALSE);
g_nDialogY = GetDlgItemInt(hWnd, IDC_Y, NULL, FALSE);
GetDlgItemText(hWnd, IDC_STR, g_szDialogStr, 100);
EndDialog(hWnd, 1); // DialogBox()의 리턴값이 1이 된다.
break;
case IDCANCEL:
// 다이얼로그 작업을 취소할 경우 그대로 나간다.
EndDialog(hWnd, FALSE); // DialogBox()의 리턴값이 0이 된다.
return TRUE;
default:
break;
}
default:
return FALSE;
break;
}
return FALSE;
}
모델리스 다이얼로그로 데이터 변경하기
// 모델리스 다이얼로그 생성
// IsWindow() 함수로 현재 유효한 윈도우 핸들인지 확인한다.
if (IsWindow(g_hModelessDialog) == FALSE) {
// CreateDialog()는 생성한 다이얼로그의 핸들을 바로 반환한다.
g_hModelessDialog = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG2), hWnd, DialogProc_Modeless);
// 다이얼로그를 생성했지만 아직 보이지 않기 때문에 화면에 보이도록 한다.
// 디이얼로그 폼 속성에서 Visible 속성에 true(WS_VISIBLE 스타일)를 주었다면
// ShowWindow() 함수를 호출하지 않아도 되지만
// 디폴트가 WS_VISIBLE 스타일이 아니므로 호출한다.
ShowWindow(g_hModelessDialog, SW_SHOW);
}
// 모델리스 다이얼로그 프로시저
INT_PTR CALLBACK DialogProc_Modeless(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
SetDlgItemInt(hWnd, IDC_X, g_nDialogX, FALSE);
SetDlgItemInt(hWnd, IDC_Y, g_nDialogY, FALSE);
SetDlgItemText(hWnd, IDC_STR, g_szDialogStr);
return true;
case WM_COMMAND:
switch (wParam)
{
case ID_CHANGE:
g_nDialogX = GetDlgItemInt(hWnd, IDC_X, NULL, FALSE);
g_nDialogY = GetDlgItemInt(hWnd, IDC_Y, NULL, FALSE);
GetDlgItemText(hWnd, IDC_STR, g_szDialogStr, 100);
InvalidateRect(g_hMainWnd, NULL, TRUE); // 데이터 변경을 메인윈도우의 화면에 반영
break;
case ID_CLOSE:
DestroyWindow(g_hModelessDialog); // CreateDialog()로 열었으므로 DestroyWindow()로 닫는다.
//EndDialog(hWnd, FALSE); // 닫히긴하지만 다시 열리지 않는다.
return TRUE;
default:
break;
}
default:
return FALSE;
break;
}
return FALSE;
}
폼 기반의 윈도우 프로그래밍
- WinMain에서 메인 윈도우를 생성하지 않고 DialogBox를 생성하는 방식
#include "Windows.h"
#include "resource.h"
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
switch (wParam)
{
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
break;
default:
break;
}
break;
default:
break;
}
return false;
}
공통 대화상자(Common Dialog)
- 윈도우즈용 프로그램들이 공통으로 사용하는 다이얼로그
- 자주 사용되는 다이얼로그를 운영체제가 제공하는 것
- 종류
- 파일 열기, 폰트 선택, 색상 선택, 인쇄, 찾기, 바꾸기
파일 열기 다이얼로그
OPENFILENAME 구조체
typedef struct tagOFNA {
DWORD lStructSize; //
HWND hwndOwner; //
HINSTANCE hInstance; //
LPCSTR lpstrFilter; //
LPSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex; //
LPSTR lpstrFile; //
DWORD nMaxFile; //
LPSTR lpstrFileTitle; //
DWORD nMaxFileTitle; //
LPCSTR lpstrInitialDir; //
LPCSTR lpstrTitle; //
DWORD Flags; //
WORD nFileOffset;
WORD nFileExtension; //
LPCSTR lpstrDefExt;
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;
LPCSTR lpTemplateName;
#ifdef _MAC
LPEDITMENU lpEditInfo;
LPCSTR lpstrPrompt;
#endif
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAMEA, *LPOPENFILENAMEA;
- DWORD lStructSize
- OPENFILENAME 구조체의 크기를 지정
- HWND hwndOwner
- 부모 윈도우 핸들, 없을 경우 NULL
- HINSTANCE hInstance
- 별도의 다이얼로그 템플리트를 사용할 경우 리소스를 가진 인스턴스 핸들을 지정
- 필요없으면 NULL 지정
- LPCSTR lpstrFilter
- 파일 형식 콤보 박스에 나타날 필터들
- 널 문자로 구분하여 여러개의 필터를 지정할 수 있다.
- 반드시 다음과 같은 형식을 따라야한다.
- “파일형식 설명1\0필터1\0파일형식 설명2\0필터2\0…”
- “Every File(.)\0*.*\0”
- DWORD nFilterIndex
- 파일 형식 콤보 박스에서 사용할 인덱스를 지정
- 여기에 지정한 인덱스에 해당하는 필터를 기본 선택함
- 0 : 커스텀 필터, 1 : lpstrFilter의 첫번째 필터, …
- 파일 다이얼로그 리턴 시 사용자가 선택한 파일의 필터가 대입됨
- LPSTR lpstrFile
- 파일 이름 에디트에 처음 나타낼 파일명
- 필요없을 경우 NULL
- 사용자가 최종적으로 선택한 파일의 이름을 리턴하는 용도로 사용됨
- DWORD nMaxFile
- lpstrFile 멤버의 길이
- 최소 256
- LPSTR lpstrFileTitle
- 파일 이름을 돌려 받기 위한 버퍼를 제공
- 파일의 경로는 포함되지 않는다.
- DWORD nMaxFileTitle
- lpstrFileTitle 멤버의 길이
- 반드시 static 변수 또는 전역변수로 선언해야한다.
- LPCSTR lpstrInitialDir
- 파일 찾기를 시작할 디렉토리 지정
- LPCSTR lpstrTitle
- 파일 다이얼로그의 타이틀 문자열
- DWORD Flags
- 파일 다이얼로그의 모양과 동작을 지정하는 옵션을 설정하는 플래그
- WORD nFileExtension
- lpstrFile 버퍼 내의 파일 확장자 오프셋을 리턴
- 사용자가 확장자를 지정하지 않으면 오프셋은 널 종료 문자열을 가리킴
- .만 찍어 확장자가 없음을 분명히 했다면 0이 됨
파일 열기 다이얼로그 생성 - GetOpenFileName()
#include "Windows.h"
#include "resource.h"
#include <stdio.h> // sprintf_s()
#include <commdlg.h> // 없으면 GetOpenFileName() 호출 시 에러남
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
HINSTANCE g_hInstance;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) {
g_hInstance = hInstance;
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}
//char lpstrFile[MAX_PATH];
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
SetWindowText(hWnd, "My Dialog");
break;
case WM_COMMAND:
switch (wParam)
{
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
break;
case IDC_OPEN:
{
// static 또는 전역변수로 선언하지 않으면 다이얼로그가 열리지 않음
static char lpstrFile[MAX_PATH];
// static 또는 전역변수로 선언하지 않아도 다이얼로그가 열림(강의에서는 필요하다고 함)
char lpstrFileTitle[MAX_PATH];
OPENFILENAME ofn = { 0, };
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = "Every File(*.*)\0*.*\0";
ofn.lpstrFile = lpstrFile;
ofn.lpstrFileTitle = lpstrFileTitle;
ofn.nMaxFile = MAX_PATH;
ofn.nMaxFileTitle = MAX_PATH;
ofn.lpstrTitle = "title";
ofn.lpstrInitialDir = "C:\\";
if (GetOpenFileName(&ofn)) {
char szResult[MAX_PATH];
sprintf_s(szResult, "Path: %s\n%s", ofn.lpstrFile, ofn.lpstrFileTitle);
MessageBox(hWnd, szResult, NULL, MB_OK);
};
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
파일 저장 다이얼로그 생성 - GetSaveFileName()
- GetOpenFileName()과 대부분의 코드가 동일
- lpstrFilter
- 저장할 파일의 형식을 구분
- 저장 시 확장자 유무를 파악하여 확장자 추가 결정
- GetSaveFileName() 리턴 후 nFileExtension이 0이면 사용자가 확장자를 지정하지 않은 것.
- 전체경로와 파일명 뒤에 확장자를 붙여준다.
- strcat() 사용
- GetSaveFileName() 리턴 후 nFileExtension이 0이면 사용자가 확장자를 지정하지 않은 것.
이미지 뷰어(폼 기반)
- 이미지 파일 오픈 시 메모리 DC에 이미지를 그리고 WM_PAINT 메시지에서 화면에 출력한다.
#include "Windows.h"
#include "resource.h"
char g_szFile[MAX_PATH];
char g_szFileTitle[MAX_PATH];
HDC g_hMemDC;
BITMAP g_bitmapHeader;
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
BitBlt(hdc, 0, 0, g_bitmapHeader.bmWidth, g_bitmapHeader.bmHeight, g_hMemDC, 0, 0, SRCCOPY);
TextOut(hdc, 10, 200, g_szFile, strlen(g_szFile));
TextOut(hdc, 10, 230, g_szFileTitle, strlen(g_szFileTitle));
EndPaint(hWnd, &ps);
}
break;
case WM_COMMAND:
switch (wParam)
{
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
break;
case IDC_OPEN:
{
OPENFILENAME ofn = { 0, };
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hWnd;
ofn.lpstrFilter = "Image File(*.bmp)\0*.bmp\0";
ofn.nFilterIndex = 1;
ofn.lpstrFile = g_szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFileTitle = g_szFileTitle;
ofn.nMaxFileTitle = MAX_PATH;
if (GetOpenFileName(&ofn) != 0) {
strcpy_s(g_szFile, ofn.lpstrFile);
strcpy_s(g_szFileTitle, ofn.lpstrFileTitle);
// ===== Image Load =====
// LoadImage
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, g_szFileTitle, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
// bitmap 정보 읽기
GetObject(hBitmap, sizeof(BITMAP), &g_bitmapHeader);
// get current dc
HDC hdc = GetDC(hWnd);
// memory dc
if (g_hMemDC != NULL) { // 해제하지 않아도 에러는 안난다.
DeleteDC(g_hMemDC);
}
g_hMemDC = CreateCompatibleDC(hdc);
// select object
SelectObject(g_hMemDC, hBitmap);
DeleteObject(hBitmap);
ReleaseDC(hWnd, hdc);
InvalidateRect(hWnd, NULL, true);
// ===== Image Load ===== end
};
}
default:
break;
}
break;
case WM_DESTROY:
if (g_hMemDC != NULL) { // 해제하지 않아도 에러는 안난다.
DeleteDC(g_hMemDC);
}
break;
default:
break;
}
return 0;
}