대화 상자 사용
대화 상자를 사용하여 정보를 표시하고 사용자의 입력을 묻는 메시지를 표시합니다. 애플리케이션은 대화 상자를 로드 및 초기화하고, 사용자 입력을 처리하고, 사용자가 작업을 완료할 때 대화 상자를 삭제합니다. 대화 상자를 처리하는 프로세스는 대화 상자가 모달인지 모달인지 모덜리스인지에 따라 달라집니다. 모달 대화 상자를 사용하려면 사용자가 애플리케이션의 다른 창을 활성화하기 전에 대화 상자를 닫아야 합니다. 그러나 사용자는 다른 애플리케이션에서 창을 활성화할 수 있습니다. 모덜리스 대화 상자에는 사용자의 즉각적인 응답이 필요하지 않습니다. 컨트롤을 포함하는 기본 창과 비슷합니다.
다음 섹션에서는 두 유형의 대화 상자를 모두 사용하는 방법을 설명합니다.
메시지 상자 표시
가장 간단한 형태의 모달 대화 상자는 메시지 상자입니다. 대부분의 애플리케이션은 메시지 상자를 사용하여 사용자에게 오류를 경고하고 오류가 발생한 후 진행 방법에 대한 지침을 묻는 메시지를 표시합니다. MessageBox 또는 MessageBoxEx 함수를 사용하여 메시지 상자 와 표시할 단추의 수와 유형을 지정하여 메시지 상자를 만듭니다. 시스템은 모달 대화 상자를 만들어 자체 대화 상자 템플릿 및 프로시저를 제공합니다. 사용자가 메시지 상자를 닫으면 MessageBox 또는 MessageBoxEx 는 메시지 상자를 닫기 위해 사용자가 선택한 단추를 식별하는 값을 반환합니다.
다음 예제에서 애플리케이션은 오류 조건이 발생한 후 사용자에게 작업을 요청하는 메시지 상자를 표시합니다. 메시지 상자에는 오류 조건 및 resolve 방법을 설명하는 메시지가 표시됩니다. MB_YESNO 스타일은 사용자가 진행 방법을 선택할 수 있는 두 개의 단추를 제공하도록 MessageBox에 지시합니다.
int DisplayConfirmSaveAsMessageBox()
{
int msgboxID = MessageBox(
NULL,
L"temp.txt already exists.\nDo you want to replace it?",
L"Confirm Save As",
MB_ICONEXCLAMATION | MB_YESNO
);
if (msgboxID == IDYES)
{
// TODO: add code
}
return msgboxID;
}
다음 이미지는 이전 코드 예제의 출력을 보여줍니다.
모달 대화 상자 만들기
DialogBox 함수를 사용하여 모달 대화 상자를 만듭니다. 대화 상자 템플릿 리소스의 식별자 또는 이름과 대화 상자 프로시저에 대한 포인터를 지정해야 합니다. DialogBox 함수는 템플릿을 로드하고 대화 상자를 표시하며 사용자가 대화 상자를 닫을 때까지 모든 사용자 입력을 처리합니다.
다음 예제에서 애플리케이션은 사용자가 애플리케이션 메뉴에서 항목 삭제 를 클릭하면 모달 대화 상자를 표시합니다. 대화 상자에는 편집 컨트롤(사용자가 항목 이름을 입력함) 및 확인 및 취소 단추가 포함되어 있습니다. 이러한 컨트롤의 컨트롤 식별자는 각각 ID_ITEMNAME, IDOK 및 IDCANCEL입니다.
예제의 첫 번째 부분은 모달 대화 상자를 만드는 문으로 구성됩니다. 이러한 문은 애플리케이션의 기본 창에 대한 창 프로시저에서 시스템에서 IDM_DELETEITEM 메뉴 식별자가 있는 WM_COMMAND 메시지를 받으면 대화 상자를 만듭니다. 이 예제의 두 번째 부분은 편집 컨트롤의 내용을 검색하고 WM_COMMAND 메시지를 받으면 대화 상자를 닫는 대화 상자 프로시저입니다.
다음 문은 모달 대화 상자를 만듭니다. 대화 상자 템플릿은 애플리케이션의 실행 파일의 리소스이며 리소스 식별자가 DLG_DELETEITEM.
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_DELETEITEM:
if (DialogBox(hinst,
MAKEINTRESOURCE(DLG_DELETEITEM),
hwnd,
(DLGPROC)DeleteItemProc)==IDOK)
{
// Complete the command; szItemName contains the
// name of the item to delete.
}
else
{
// Cancel the command.
}
break;
}
return 0L;
이 예제에서 애플리케이션은 해당 기본 창을 대화 상자의 소유자 창으로 지정합니다. 시스템이 처음에 대화 상자를 표시하면 해당 위치는 소유자 창의 클라이언트 영역의 왼쪽 위 모서리를 기준으로 합니다. 애플리케이션은 DialogBox 의 반환 값을 사용하여 작업을 진행할지 취소할지 여부를 결정합니다. 다음 문은 대화 상자 프로시저를 정의합니다.
char szItemName[80]; // receives name of item to delete.
BOOL CALLBACK DeleteItemProc(HWND hwndDlg,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
if (!GetDlgItemText(hwndDlg, ID_ITEMNAME, szItemName, 80))
*szItemName=0;
// Fall through.
case IDCANCEL:
EndDialog(hwndDlg, wParam);
return TRUE;
}
}
return FALSE;
}
이 예제에서 프로시저는 GetDlgItemText 를 사용하여 ID_ITEMNAME 식별된 편집 컨트롤에서 현재 텍스트를 검색합니다. 그런 다음 EndDialog 함수를 호출하여 받은 메시지에 따라 대화 상자의 반환 값을 IDOK 또는 IDCANCEL로 설정하고 대화 상자를 닫는 프로세스를 시작합니다. IDOK 및 IDCANCEL 식별자는 확인 및 취소 단추에 해당합니다. 프로시저가 EndDialog를 호출한 후 시스템은 추가 메시지를 프로시저에 보내 대화 상자를 삭제하고 대화 상자의 반환 값을 대화 상자를 만든 함수로 반환합니다.
모덜리스 대화 상자 만들기
CreateDialog 함수를 사용하여 대화 상자 템플릿 리소스의 식별자 또는 이름 및 대화 상자 프로시저에 대한 포인터를 지정하여 모덜리스 대화 상자를 만듭니다. CreateDialog 는 템플릿을 로드하고, 대화 상자를 만들고, 필요에 따라 표시합니다. 애플리케이션은 대화 상자 프로시저에 사용자 입력 메시지를 검색하고 디스패치할 책임이 있습니다.
다음 예제에서 애플리케이션은 사용자가 애플리케이션 메뉴에서 이동 을 클릭할 때 모덜리스 대화 상자(아직 표시되지 않은 경우)를 표시합니다. 대화 상자에는 편집 컨트롤, 검사 상자, 확인 및 취소 단추가 포함되어 있습니다. 대화 상자 템플릿은 애플리케이션의 실행 파일에 있는 리소스이며 리소스 식별자가 DLG_GOTO. 사용자가 편집 컨트롤에 줄 번호를 입력하고 검사 상자를 확인하여 줄 번호가 현재 줄을 기준으로 함을 지정합니다. 컨트롤 식별자는 ID_LINE, ID_ABSREL, IDOK 및 IDCANCEL입니다.
예제의 첫 번째 부분에 있는 문은 모덜리스 대화 상자를 만듭니다. 이러한 문은 애플리케이션의 기본 창에 대한 창 프로시저에서 창 프로시저가 IDM_GOTO 메뉴 식별자가 있는 WM_COMMAND 메시지를 받을 때 대화 상자를 만들지만 전역 변수에 유효한 핸들이 아직 없는 경우에만 만듭니다. 예제의 두 번째 부분은 애플리케이션의 기본 메시지 루프입니다. 루프에는 사용자가 이 모덜리스 대화 상자에서 대화 상자 키보드 인터페이스를 사용할 수 있도록 하는 IsDialogMessage 함수가 포함되어 있습니다. 예제의 세 번째 부분은 대화 상자 프로시저입니다. 프로시저는 사용자가 확인 단추를 클릭할 때 편집 컨트롤 및 검사 상자의 내용을 검색합니다. 사용자가 취소 단추를 클릭하면 프로시저가 대화 상자를 삭제합니다.
HWND hwndGoto = NULL; // Window handle of dialog box
...
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_GOTO:
if (!IsWindow(hwndGoto))
{
hwndGoto = CreateDialog(hinst,
MAKEINTRESOURCE(DLG_GOTO),
hwnd,
(DLGPROC)GoToProc);
ShowWindow(hwndGoto, SW_SHOW);
}
break;
}
return 0L;
앞의 문에서 CreateDialog 는 유효한 창 핸들이 없는 경우에만 hwndGoto
호출됩니다. 이렇게 하면 애플리케이션에 두 개의 대화 상자가 동시에 표시되지 않습니다. 이 확인 방법을 지원하려면 대화 상자를 삭제할 때 대화 프로시저를 NULL 로 설정해야 합니다.
애플리케이션에 대한 메시지 루프는 다음 문으로 구성됩니다.
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// Handle the error and possibly exit
}
else if (!IsWindow(hwndGoto) || !IsDialogMessage(hwndGoto, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
루프는 대화 상자에 대한 창 핸들의 유효성을 확인하고 핸들이 유효한 경우에만 IsDialogMessage 함수를 호출합니다. IsDialogMessage 는 대화 상자에 속한 경우에만 메시지를 처리합니다. 그렇지 않으면 FALSE 를 반환하고 루프는 메시지를 적절한 창으로 디스패치합니다.
다음 문은 대화 상자 프로시저를 정의합니다.
int iLine; // Receives line number.
BOOL fRelative; // Receives check box status.
BOOL CALLBACK GoToProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
BOOL fError;
switch (message)
{
case WM_INITDIALOG:
CheckDlgButton(hwndDlg, ID_ABSREL, fRelative);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
fRelative = IsDlgButtonChecked(hwndDlg, ID_ABSREL);
iLine = GetDlgItemInt(hwndDlg, ID_LINE, &fError, fRelative);
if (fError)
{
MessageBox(hwndDlg, SZINVALIDNUMBER, SZGOTOERR, MB_OK);
SendDlgItemMessage(hwndDlg, ID_LINE, EM_SETSEL, 0, -1L);
}
else
// Notify the owner window to carry out the task.
return TRUE;
case IDCANCEL:
DestroyWindow(hwndDlg);
hwndGoto = NULL;
return TRUE;
}
}
return FALSE;
}
앞의 문에서 프로시저는 WM_INITDIALOG 처리하고 메시지를 WM_COMMAND . WM_INITDIALOG 처리하는 동안 프로시저는 전역 변수의 현재 값을 CheckDlgButton에 전달하여 검사 상자를 초기화합니다. 그런 다음, TRUE를 반환 하여 시스템에 기본 입력 포커스를 설정하도록 지시합니다.
WM_COMMAND 처리하는 동안 프로시저는 사용자가 취소 단추를 클릭하는 경우에만 대화 상자를 닫습니다. 즉, IDCANCEL 식별자가 있는 단추입니다. 프로시저는 DestroyWindow 를 호출하여 모덜리스 대화 상자를 닫아야 합니다. 또한 이 변수에 의존하는 다른 문이 올바르게 작동하는지 확인하기 위해 이 프로시저는 변수를 NULL 로 설정합니다.
사용자가 확인 단추를 클릭하면 프로시저는 검사 상자의 현재 상태를 검색하여 fRelative 변수에 할당합니다. 그런 다음 변수를 사용하여 편집 컨트롤에서 줄 번호를 검색합니다. GetDlgItemInt 는 편집 컨트롤의 텍스트를 정수로 변환합니다. fRelative 값은 함수가 숫자를 부호 있는 값 또는 부호 없는 값으로 해석하는지 여부를 결정합니다. 편집 컨트롤 텍스트가 유효한 숫자가 아닌 경우 GetDlgItemInt 는 fError 변수의 값을 0이 아닌 값으로 설정합니다. 프로시저는 이 값을 검사하여 오류 메시지를 표시할지 아니면 작업을 수행할지 여부를 결정합니다. 오류가 발생할 경우 대화 상자 프로시저는 편집 컨트롤에 메시지를 보내 사용자가 쉽게 바꿀 수 있도록 컨트롤의 텍스트를 선택하도록 지시합니다. GetDlgItemInt에서 오류를 반환하지 않으면 프로시저에서 요청된 작업 자체를 수행하거나 소유자 창에 메시지를 보내 작업을 수행하도록 지시할 수 있습니다.
대화 상자 초기화
WM_INITDIALOG 메시지를 처리하는 동안 대화 상자 및 해당 내용을 초기화합니다. 가장 일반적인 작업은 현재 대화 상자 설정을 반영하도록 컨트롤을 초기화하는 것입니다. 또 다른 일반적인 작업은 화면 또는 소유자 창 내에서 대화 상자를 가운데에 배치하는 것입니다. 일부 대화 상자의 유용한 작업은 기본 입력 포커스를 수락하지 않고 입력 포커스를 지정된 컨트롤로 설정하는 것입니다.
다음 예제에서 대화 상자 프로시저는 대화 상자를 가운데에 두고 WM_INITDIALOG 메시지를 처리하는 동안 입력 포커스를 설정합니다. 대화 상자를 가운데에 배치하기 위해 프로시저는 대화 상자 및 소유자 창의 창 사각형을 검색하고 대화 상자의 새 위치를 계산합니다. 입력 포커스를 설정하기 위해 프로시저는 wParam 매개 변수를 검사하여 기본 입력 포커스의 식별자를 확인합니다.
HWND hwndOwner;
RECT rc, rcDlg, rcOwner;
....
case WM_INITDIALOG:
// Get the owner window and dialog box rectangles.
if ((hwndOwner = GetParent(hwndDlg)) == NULL)
{
hwndOwner = GetDesktopWindow();
}
GetWindowRect(hwndOwner, &rcOwner);
GetWindowRect(hwndDlg, &rcDlg);
CopyRect(&rc, &rcOwner);
// Offset the owner and dialog box rectangles so that right and bottom
// values represent the width and height, and then offset the owner again
// to discard space taken up by the dialog box.
OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
OffsetRect(&rc, -rc.left, -rc.top);
OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
// The new position is the sum of half the remaining space and the owner's
// original position.
SetWindowPos(hwndDlg,
HWND_TOP,
rcOwner.left + (rc.right / 2),
rcOwner.top + (rc.bottom / 2),
0, 0, // Ignores size arguments.
SWP_NOSIZE);
if (GetDlgCtrlID((HWND) wParam) != ID_ITEMNAME)
{
SetFocus(GetDlgItem(hwndDlg, ID_ITEMNAME));
return FALSE;
}
return TRUE;
앞의 문에서 프로시저는 GetParent 함수를 사용하여 대화 상자에 대한 소유자 창 핸들을 검색합니다. 함수는 대화 상자에 소유자 창 핸들을 반환하고 부모 창 핸들을 자식 창에 반환합니다. 애플리케이션은 소유자가 없는 대화 상자를 만들 수 있으므로 프로시저는 반환된 핸들을 확인하고 GetDesktopWindow 함수를 사용하여 필요한 경우 데스크톱 창 핸들을 검색합니다. 새 위치를 계산한 후 프로시저는 SetWindowPos 함수를 사용하여 대화 상자를 이동하고 HWND_TOP 값을 지정하여 대화 상자가 소유자 창 위에 유지되도록 합니다.
입력 포커스를 설정하기 전에 프로시저는 기본 입력 포커스의 컨트롤 식별자를 확인합니다. 시스템은 wParam 매개 변수에서 기본 입력 포커스의 창 핸들을 전달합니다. GetDlgCtrlID 함수는 창 핸들로 식별된 컨트롤의 식별자를 반환합니다. 식별자가 올바른 식별자와 일치하지 않는 경우 프로시저는 SetFocus 함수를 사용하여 입력 포커스를 설정합니다. GetDlgItem 함수는 원하는 컨트롤의 창 핸들을 검색하는 데 필요합니다.
메모리에서 템플릿 만들기
애플리케이션은 처리 중인 데이터의 현재 상태에 따라 대화 상자의 콘텐츠를 조정하거나 수정하는 경우가 있습니다. 이러한 경우 애플리케이션의 실행 파일에서 가능한 모든 대화 상자 템플릿을 리소스로 제공하는 것은 실용적이지 않습니다. 그러나 메모리에 템플릿을 만들면 애플리케이션이 모든 상황에 더 유연하게 적응할 수 있습니다.
다음 예제에서 애플리케이션은 메시지와 확인 및 도움말 단추가 포함된 모달 대화 상자에 대한 템플릿을 메모리에 만듭니다.
대화 상자 템플릿에서 대화 상자 및 단추 제목과 같은 모든 문자 문자열은 유니코드 문자열이어야 합니다. 이 예제에서는 MultiByteToWideChar 함수를 사용하여 이러한 유니코드 문자열을 생성합니다.
대화 상자 템플릿의 DLGITEMTEMPLATE 구조체는 DWORD 경계에 맞춰야 합니다. 이러한 구조를 정렬하기 위해 이 예제에서는 입력 포인터를 사용하고 DWORD 경계에 정렬된 가장 가까운 포인터를 반환하는 도우미 루틴을 사용합니다.
#define ID_HELP 150
#define ID_TEXT 200
LPWORD lpwAlign(LPWORD lpIn)
{
ULONG ul;
ul = (ULONG)lpIn;
ul ++;
ul >>=1;
ul <<=1;
return (LPWORD)ul;
}
LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPSTR lpszMessage)
{
HGLOBAL hgbl;
LPDLGTEMPLATE lpdt;
LPDLGITEMTEMPLATE lpdit;
LPWORD lpw;
LPWSTR lpwsz;
LRESULT ret;
int nchar;
hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024);
if (!hgbl)
return -1;
lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
// Define a dialog box.
lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
lpdt->cdit = 3; // Number of controls
lpdt->x = 10; lpdt->y = 10;
lpdt->cx = 100; lpdt->cy = 100;
lpw = (LPWORD)(lpdt + 1);
*lpw++ = 0; // No menu
*lpw++ = 0; // Predefined dialog box class (by default)
lpwsz = (LPWSTR)lpw;
nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "My Dialog", -1, lpwsz, 50);
lpw += nchar;
//-----------------------
// Define an OK button.
//-----------------------
lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary
lpdit = (LPDLGITEMTEMPLATE)lpw;
lpdit->x = 10; lpdit->y = 70;
lpdit->cx = 80; lpdit->cy = 20;
lpdit->id = IDOK; // OK button identifier
lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;
lpw = (LPWORD)(lpdit + 1);
*lpw++ = 0xFFFF;
*lpw++ = 0x0080; // Button class
lpwsz = (LPWSTR)lpw;
nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "OK", -1, lpwsz, 50);
lpw += nchar;
*lpw++ = 0; // No creation data
//-----------------------
// Define a Help button.
//-----------------------
lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary
lpdit = (LPDLGITEMTEMPLATE)lpw;
lpdit->x = 55; lpdit->y = 10;
lpdit->cx = 40; lpdit->cy = 20;
lpdit->id = ID_HELP; // Help button identifier
lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
lpw = (LPWORD)(lpdit + 1);
*lpw++ = 0xFFFF;
*lpw++ = 0x0080; // Button class atom
lpwsz = (LPWSTR)lpw;
nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "Help", -1, lpwsz, 50);
lpw += nchar;
*lpw++ = 0; // No creation data
//-----------------------
// Define a static text control.
//-----------------------
lpw = lpwAlign(lpw); // Align DLGITEMTEMPLATE on DWORD boundary
lpdit = (LPDLGITEMTEMPLATE)lpw;
lpdit->x = 10; lpdit->y = 10;
lpdit->cx = 40; lpdit->cy = 20;
lpdit->id = ID_TEXT; // Text identifier
lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
lpw = (LPWORD)(lpdit + 1);
*lpw++ = 0xFFFF;
*lpw++ = 0x0082; // Static class
for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*lpszMessage++;);
lpw = (LPWORD)lpwsz;
*lpw++ = 0; // No creation data
GlobalUnlock(hgbl);
ret = DialogBoxIndirect(hinst,
(LPDLGTEMPLATE)hgbl,
hwndOwner,
(DLGPROC)DialogProc);
GlobalFree(hgbl);
return ret;
}