API- 대화상자
- 프로그래밍/WindowsAPI
- 2011. 5. 26. 17:08
1.정의
대화상자에는 컨트롤들이 배치
사용자는 대화상자를 호출한 후 컨트롤을 통해 자신의 의사를 표시하고 명령
프로그램은 대화상자에 배치된 컨트롤을 통해 현재 상태를 사용자에게 보여준다.
사용자로부터 끊임없이 지시를 받고 사용자의 지시에 따라 작업을 하고 작업한 결과를 사용자에게 보고한다.
프로그램과 사용자와의 이런 대화 수단으로 간단하게는 버튼, 에디트, 리스트 박스 등의 컨트롤이 사용된다. 그러나 이런 간단한 컨트롤로는 사용자로부터 복잡한 정보를 입력 받기가 어려우며 많은 양의 정보를 효율적으로 입력받기 위해 주로 대화상자를 사용한다.
2.대화상자의 종류
모달형과 모델리스형이 있다.
1)모달(Modal)형
대화상자를 닫기 전에 다른 윈도우로 전환할 수 없으며 반드시 OK버튼이나 Cancel 버튼을 눌러 대화상자를 닫아야 다른 윈도우로 전환할 수 있다. 개발자 스튜디오의 Open대화상자가 대표적인 모달 대화상자의 예이다.
파일을 선택하거나 Cancel 버튼을 눌러 이 대화상자를 닫기 전에는 에디터나 프로젝트 워크 스페이스를 사용할 수 없다. 반드시 파일을 선택하거나 아니면 선택을 취소해서 대화상자를 닫아야 다른 일을 할 수 있다. 심지어 메뉴나 툴바를 선택하는 일도 할 수 없다. 그러나 모달형이라 하더라도 다른 프로그램으로는 전환할 수 있다. 즉 Open 대화상자가 열려 있는 상태에서 탐색기로 파일 관리를 하거나 테트리스 게임을 즐길 수는 있다. 참고로 MessageBox 함수에 의해 만들어지는 메시지 박스도 모달형 대화상자이다. 메시지 박스에 출력된 메시지를 다 읽고 OK버튼을 누르기 전에는 절대로 다른 일을 할 수 없도록 되어 있다.
2)모델리스(Modeless)형
대화상자를 열어 놓은 채로 다른 윈도우로 전환할 수 있는 대화상자이다. 일반적으로 모달형에 비해 많이 사용되지는 않지만 그 예를 찾는다면 워드 프로그램의 찾기(ctr+F) 대화상자를 들 수 있다.
문자열을 찾으면서 검색된 문자열이 있는 부분을 즉시 편집할 수 있도록 하기 위해 대화상자가 열려있는 채로 다른 작업을 할 수 있게 해 준다. 프로그램의 상태를 나타내거나 작업을 하면서 참조해야 할 여러가지 정보를 보여 주는 윈도우가 모델리스형으로 만들어진다. 그러나 모델리스형은 다른 작업을 하면서도 열려 있을 수 있기 때문에 여러 가지 문제가 생길 수 있으며 훨씬 더 프로그래밍하기가 까다롭다는 단점이 있다.
3.대화상자 만들기
대화상자를 만들기 위해서는 기본적으로 다음 두가지가 있어야 한다. 반드시 있어야한다.
1)대화상자 템플리트:
대화상자의 모양과 대화상자내의 컨트롤 배치 상태가 저장되는 이진 정보이며 리소스로 작성된다.
개발자 스튜디오에 별도의 대화상자 편집기가 제공되므로 대화상자를 어렵지 않게 디자인할 수 있다.
2)대화상자 프로시저:
윈도우 프로시저가 윈도우에서 발생하는 메시지를 처리하는 것과 마찬가지로 대화상자 프로시저는 대화상자에서 발생하는 메시지를 처리한다.(CALLBACK 함수)
모든 대화상자는 이 두가지가 있어야만 만들 수 있다.
대화상자 템플리트는 모양을 정의하며
대화상자 프로시저는 동작을 정의한다.
간단한 대화상자를 만들고 보여주는 About 프로젝트를 작성해 보자.
예제)
*템플릿 소스를 작성해보자.
대화상자의 겉모양을 만들기 위해 대화상자 템플리트 리소스를 작성한다.
리소스 메뉴에서 Insert/Resource/Dialog를 선택한다.
그러면 IDD_DIALOG1이라는 이름을 가지는 빈 대화상자와 컨트롤 팔레트가 열릴 것이다.
대화상자에는 OK, CANCEL 두 개의 버튼이 이미 배치되어 있으며 컨트롤 팔레트에는 대화상자에 배치할 수 있는 여러가지 컨트롤들이 나열되어 있다.
이 중 세번째 버튼 를 눌러 문자열을 대화상자에 배치한다.
디폴트로 스태틱 컨트롤은 "Static"이라는 캡션을 가지고 있는데 이 캡션을 다른 캡션으로 변경하도록 하자.
대화상자에 배치된 스태택 컨트롤의 팝업 메뉴에서 Properties를 선택하면 속성 편집기가 열린다.
이 리소스를 About.rc라는 이름으로 저장한 후 프로젝트에 포함시킨다.
리소스 파일에 대화상자 템플리트가 포함되었으며 이 템플리트는 대화상자의 모양을 기억하고 있다.
* 대화상자 프로시저를 작성한다.
전술한 바와 같이 대화상자 프로시저는 대화상자내에서 발생하는 메시지들을 처리하는 함수이다.
윈도우 프로시저와 개념상 동일한 역할을 하며 받아들이는 인수도 동일하다. (CALLBACK함수)
다음과 같이 코딩하도록 하되 WndProc에서 이 함수를 참조하므로 WinMain 바로 다음에, WndProc 앞에 기입하면 된다.
#include "resource.h" BOOL CALLBACK AboutDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam) { switch(iMessage) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch (wParam) { case IDOK: case IDCANCEL: EndDialog(hDlg,0); return TRUE; } break; } return FALSE; }
리소스를 만들었으므로 선두에 resource.h를 포함시켰다. 이 프로시저의 내용은 잠시 후에 분석해 보도록 하자.
* 마지막으로 WndProc에서 일정한 때에 대화상자를 호출하도록 한다.
마우스 왼쪽 버튼을 눌렀을 때 대화상자가 나타나도록 해 보자. WM_LBUTTONDOWN 메시지에서 대화상자를 호출하도록 하면 될 것 같다.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { switch(iMessage) { case WM_LBUTTONDOWN: DialogBox(g_hInst,MAKEINTRESOURCE(IDD_DIALOG1),hWnd,AboutDlgProc); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }int DialogBox( HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc );
대화상자를 호출할 때는 DialogBox() 함수를 사용한다.
첫번째 인수는 대화상자 리소스를 가진 인스턴스의 핸들이며
두번째 인수는 대화상자 템플리트의 리소스 ID이다.
세번째 인수는 대화상자를 소유할 부모 윈도우이며
네번째 인수는 대화상자 프로시저의 이름이다.
DialogBox 함수는 hInstance 인스턴스의 리소스 중 lpTemplate가 지정하는 대화상자 템플리트를 읽어와 대화상자를 만들어 화면으로 보여줄 것이다. 그리고 lpDialogFunc 함수로 대화상자 운용에 필요한 메시지를 보내주어 대화상자가 제대로 실행될 수 있도록 해주며 대화상자가 종료되면 리턴한다. 이때 리턴되는 값은 대화상자의 종료 함수인 EndDialog 함수가 전달하는 값이다.
위 함수 호출문은 "g_hInst 인스턴스에 정의된 IDD_DIALOG1 대화상자 템플리트로 대화상자를 만들되 이 대화상자의 메시지는 AboutDlgProc 함수가 처리한다"는 뜻이다. 프로그램을 컴파일시키고 실행해 보자. 작업영역에서 마우스 왼쪽 버튼을 누르면 대화상자가 나타날 것이다.
모달형의 대화상자이므로 이 대화상자를 닫기 전에는 부모 윈도우를 조작할 수는 없다. OK나 Cancel 버튼을 누르면 대화상자가 닫힌다.
4.대화상자 프로시져
대화상자 프로시저와 윈도우 프로시저는 비슷한 역할을 하지만 차이점도 있다.
첫째로 두 프로시저의 리턴값이 다르다. 윈도우 프로시저는 LRESULT(long)형의 값을 리턴하지만 대화상자 프로시저는 BOOL형의 값을 리턴한다. 대화상자 프로시저는 메시지를 제대로 처리했으면 TRUE를 리턴하고 메시지를 처리하지 못했으면 FALSE를 리턴하기로 약속되어 있다. 만약 대화상자 프로시저가 메시지를 처리하지 못해 FALSE를 리턴했다면 그 메시지에 대한 나머지 처리는 윈도우즈가 알아서 해준다. 그래서 대화상자 프로시저는 DefWindowProc을 호출할 필요가 없으며 대신 FALSE를 리턴해 주면 된다.
두번째로
대화상자 프로시저는 WM_CREATE 메시지 대신
WM_INITDIALOG
메시지
를 받아들이며 이 메시지를 받은 시점에서 대화상자에 필요한 초기화를 해 주면 된다. 예제에서는 WM_INITDIALOG에서 TRUE값을 리턴하기만 하여 초기화가 완료되었음을 윈도우즈에게 알린다. 초기화할 내용이 있다면, 예를 들어 특정 컨트롤에 포커스를 준다거나 대화상자의 위치를 바꾼다거나 할 일이 있다면 return TRUE; 앞에 초기화 코드를 작성하면 된다.
대화상자 프로시저에서 주로 처리하는 메시지는
WM_COMMAND
메시지이다. 이 메시지는 대화상자에서 컨트롤이 선택될 경우 컨트롤들이 대화상자 프로시저로 보내는 통지 메시지이다. LOWORD(wParam)에 메시지를 보낸 컨트롤의 ID가 전달되며 HIWORD(wParam)에 통지 코드가 전달된다. 이 예제의 경우 OK버튼이나 CANCEL 버튼이 눌러질 경우 EndDialog 함수를 호출하여 대화상자를 종료하도록 하였다.
EndDialog 함수의 두번째 인수 nResult는 대화상자를 호출한 DialogBox 함수의 리턴값으로 전달된다.
대화상자 프로시저와 윈도우 프로시저는 비슷한 역할을 하지만 차이점도 있다.
첫째로 두 프로시저의 리턴값이 다르다. 윈도우 프로시저는 LRESULT(long)형의 값을 리턴하지만 대화상자 프로시저는 BOOL형의 값을 리턴한다. 대화상자 프로시저는 메시지를 제대로 처리했으면 TRUE를 리턴하고 메시지를 처리하지 못했으면 FALSE를 리턴하기로 약속되어 있다. 만약 대화상자 프로시저가 메시지를 처리하지 못해 FALSE를 리턴했다면 그 메시지에 대한 나머지 처리는 윈도우즈가 알아서 해준다. 그래서 대화상자 프로시저는 DefWindowProc을 호출할 필요가 없으며 대신 FALSE를 리턴해 주면 된다.
두번째로 대화상자 프로시저는 WM_CREATE 메시지 대신 WM_INITDIALOG 메시지를 받아들이며 이 메시지를 받은 시점에서 대화상자에 필요한 초기화를 해 주면 된다. 예제에서는 WM_INITDIALOG에서 TRUE값을 리턴하기만 하여 초기화가 완료되었음을 윈도우즈에게 알린다. 초기화할 내용이 있다면, 예를 들어 특정 컨트롤에 포커스를 준다거나 대화상자의 위치를 바꾼다거나 할 일이 있다면 return TRUE; 앞에 초기화 코드를 작성하면 된다.
대화상자 프로시저에서 주로 처리하는 메시지는 WM_COMMAND 메시지이다. 이 메시지는 대화상자에서 컨트롤이 선택될 경우 컨트롤들이 대화상자 프로시저로 보내는 통지 메시지이다. LOWORD(wParam)에 메시지를 보낸 컨트롤의 ID가 전달되며 HIWORD(wParam)에 통지 코드가 전달된다. 이 예제의 경우 OK버튼이나 CANCEL 버튼이 눌러질 경우 EndDialog 함수를 호출하여 대화상자를 종료하도록 하였다.
BOOL EndDialog(HWND hDlg,int nResult);
EndDialog 함수의 두번째 인수 nResult는 대화상자를 호출한 DialogBox 함수의 리턴값으로 전달된다.
5. 컨트롤의 값 읽기
대화상자는 사용자에게 값을 보여주거나 또는 값을 입력받는 장치이다.
그러므로 대화상자와 부모 윈도우간에 정보를 교환할 수 있는 방법이 필요하다. 그래야 대화상자가 열릴 때 보여주고자 하는 정보를 컨트롤에 출력할 수 있으며 또한 대화상자가 닫힐 때 사용자가 입력한 값을 다시 돌려 받을 수가 있다. 대화상자와 부모 윈도우간에 교환할 수 있는 정보의 종류는 크게 문자열과 정수형 두가지가 있다.
1) 우선 문자열 값을 교환하는 함수에는 컨트롤로부터 문자열을 읽는 함수와 컨트롤로 문자열을 출력하는 함수가 있다.
UINT GetDlgItemText( HWND hDlg, int nIDDlgItem, LPTSTR lpString, int nMaxCount );
BOOL SetDlgItemText( HWND hDlg, int nIDDlgItem, LPCTSTR lpString );
첫번째 인수는 대화상자의 윈도우 핸들이며
두번째 인수 nIDDlgItem은 값을 읽거나 쓸 컨트롤의 ID이다. lpString은 대입하고자 하는 문자열, 또는 문자열을 읽을 버퍼이며 문자열을 읽을 때는 버퍼의 길이를 nMaxCount로 명시해 주어야 한다.
2) 정수를 교환하는 함수
도 이와 유사하다.
UINT GetDlgItemInt( HWND hDlg, int nIDDlgItem, BOOL *lpTranslated, BOOL bSigned );
BOOL SetDlgItemInt( HWND hDlg, int nIDDlgItem, UINT uValue, BOOL bSigned );
첫번째, 두번째 인수는 문자열의 경우와 동일하다. GetDlgItemInt는 해당 컨트롤에 입력된 정수값을 읽어 리턴값으로 둘려주되 네번째 인수 bSigned가 TRUE일 경우는 부호있는 정수값을 읽어주며 FALSE일 경우는 부호를 무시하고 무조건 양수로 읽어준다. 그런데 컨트롤로부터 정수형값을 읽어들일 때는 항상 에러가 발생할 소지가 있다. 예를 들어 에디트에 입력된 정수를 읽어들일 경우 에디트에 숫자 이외의 문자가 있거나 숫자가 너무 클 경우 등이다. 이럴 경우 GetDlgItemInt는 세번째 인수로 지정된 BOOL형 포인터에 에러가 있었는지 없었는지를 대입해준다. 에러 검사를 할 필요가 없을 때는 세번째 인수로 NULL값을 주면 된다.
정수값을 대입하는 SetDlgItemInt는 지정한 컨트롤에 nValue 정수값을 대입해 준다. 잠시 후에 실습을 통해 이 함수들을 사용해 보도록 하자.
6.대화상자 예제
이번에 만들어 볼 예제는 대화상자를 통해 사용자에게 현재 설정된 값을 보여주고 또한 사용자가 대화상자를 통해 값을 변경할 수 있도록 해 주는 예제이다. 아주 전형적인 대화상자 예제이므로 아예 제작 절차를 외워둘만하다. 다음 단계를 따라 예제를 만들어 보자.
1)
먼저 InfoDlg 프로젝트를 만들고 ApiStart.txt를 복사해온 후 lpszClass를 "InfoDlg"로 수정한다.
2)
먼저 이 프로그램에서 사용할 전역 변수들을 파일 선두에 선언한다.
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE g_hInst; LPSTR lpszClass="InfoDlg"; int x; int y; char str[128];
좌표를 기억하는 값 x,y와 문자열 str 등 세 개의 전역변수가 정의되어 있다. 전역 변수는 함수 내부가 아니면 어디든 선언해도 상관없으나 이 예제에서는 WndProc과 대화상자 프로시저에서 이 값들을 참조하므로 가급적이면 소스의 선두에 선언하는 것이 좋다. 다음은 WndProc을 작성한다. 소스만 읽어보면 어떻게 동작하는 예제인지 쉽게 파악될 것이다.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch(iMessage) { case WM_CREATE: x=100; y=100; strcpy(str,"String"); return 0; case WM_PAINT: hdc=BeginPaint(hWnd, &ps); TextOut(hdc,x,y,str,strlen(str)); EndPaint(hWnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } |
WM_CREATE에서 좌표와 문자열을 각각 (100,100) 그리고 "String"으로 초기화하였다. 그리고 WM_PAINT에서 (x,y) 좌표에 str 문자열을 출력한다. 여기까지 작성한 후 프로그램을 실행해 보자.
정해진 위치에 문자열을 출력하기만 할 뿐 별다른 기능을 가지고 있지는 않다. 이제 대화상자를 하나 만들어 문자열값과 이 문자열이 출력될 좌표를 사용자가 직접 변경할 수 있도록 해 보자.
3) Insert/Resource/Dialog 항목을 선택하여 사용자로부터 값을 입력받을 대화상자를 만든다.
우리가 입력받고자 하는 값은 좌표값 두 개와 문자열 하나이므로 이 경우는 에디트 컨트롤을 세 개 배치하면 된다. 좌표를 입력받을 에디트는 위쪽에 두 개 배치하고 문자열을 입력받을 에디트는 아래쪽에 배치한 후 길이를 좀 길게 만들어 준다.
그리고 각각의 에디트 컨트롤에 IDC_X, IDC_Y, IDC_STR이라는 ID를 주도록 한다.
리소스 파일을 InfoDlg.rc로 저장한 후 프로젝트에 포함시킨다.
4)
사용자가 대화상자를 호출할 수 있도록 WndProc에 대화상자를 호출하는 코드를 삽입한다.
메뉴를 선택하거나 단축키를 누를때 호출하는 것이 적합하겠지만 일단 가장 사용하기 쉬운 WM_LBUTTONDOWN 메시지를 사용하도록 하자. WndProc에 다음 코드를 추가한다.
case WM_PAINT: hdc=BeginPaint(hWnd, &ps); TextOut(hdc,x,y,str,strlen(str)); EndPaint(hWnd, &ps); return 0; case WM_LBUTTONDOWN: if (DialogBox(g_hInst,MAKEINTRESOURCE(IDD_DIALOG1),hWnd,InfoDlgProc)==1) InvalidateRect(hWnd, NULL, TRUE); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }
DialogBox 함수로 대화상자를 호출하였으며 대화상자 프로시저명은 InfoDlgProc으로 지정하였다. 물론 잠시 후에 대화상자 프로시저를 만들어야 한다. 대화상자를 단순히 호출하기만 하는 것이 아니라 그 리턴값을 살펴보고 1이 리턴되었으면 윈도우 전체를 다시 그리도록 하였다. 1이 리턴되었다는 것은 사용자가 값을 변경한 후 OK버튼을 눌렀다는 뜻이므로 변경된 좌표에 변경된 문자열을 다시 그려 주어야 한다.
5) 마지막으로 대화상자 프로시저를 작성해 준다.
이 프로젝트의 가장 핵심이 되는 부분이다. 단 이 함수를 WndProc에서 사용하고 있으므로 WndProc 이전에 코드를 작성하거나 아니면 원형선언이라도 먼저 해 두어야 할 것이다.
#include "resource.h"
BOOL CALLBACK InfoDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_INITDIALOG:
SetDlgItemText(hDlg,IDC_STR,str);
SetDlgItemInt(hDlg,IDC_X,x,FALSE);
SetDlgItemInt(hDlg,IDC_Y,y,FALSE);
return TRUE;
case WM_COMMAND:
switch (wParam) {
case IDOK:
GetDlgItemText(hDlg,IDC_STR, str,128);
x=GetDlgItemInt(hDlg,IDC_X,NULL,FALSE);
y=GetDlgItemInt(hDlg,IDC_Y,NULL,FALSE);
EndDialog(hDlg,1);
return TRUE;
case IDCANCEL:
EndDialog(hDlg,0);
return TRUE;
}
break;
}
return FALSE;
}
대화상자 프로시저에서는 두개의 메시지만을 처리하고 있다. 우선 대화상자가 처음 만들어질 때 호출되는 WM_INITDIALOG 메시지에서는 세 개의 컨트롤에 값을 대입하여 사용자에게 현재의 좌표와 문자열을 보여주도록 한다. 만약 이 초기화를 생략해 버리면 사용자들은 현재 설정된 좌표와 문자열값을 볼 수 없을 것이다.
WM_COMMAND 메시지에서는 두 개의 컨트롤로부터 전달된 통지 메시지를 처리한다. IDOK(OK 버튼)가 전달되었을 경우는 Get* 함수를 사용하여 컨트롤로부터 값을 읽은 후 좌표와 문자열을 변경한다. 그리고 EndDialog 함수를 호출하여 대화상자를 종료하되 두번째 인수로 1을 전달하여 대화상자를 호출한 DialogBox 함수가 1을 리턴하도록 해 준다. IDCANCEL(Cancel 버튼)이 전달되었을 경우는 사용자가 입력한 값을 무시하고 EndDialog 함수를 호출하여 곧바로 대화상자를 종료해 버린다. 단 이때 Dialog 함수로 전달되는 값은 0이 되도록 하여 화면을 다시 그리지 않도록 해 준다.
그럼 이제 프로그램을 실행해 보자. 처음 실행하면 (100,100) 좌표에 String이라는 문자열이 출력되어 있을 것이다. 마우스 왼쪽 버튼을 눌러 문자열이나 좌표를 변경하고 OK버튼을 누르면 변경된 좌표에 변경된 문자열이 출력될 것이다.
대화상자와 부모 윈도우간에 값들이 어떻게 전달되는지 그 과정을 머리속으로 그려보고 정리해 보도록 하자. 이 예제를 완전히 이해했다면 프로젝트를 완전히 지운 후 처음부터 다시 만들어 보도록 하자. 아주 전형적인 윈도우즈 프로그램이므로 외워둘만한 가치가 있다.
출저: www.winapi.com
이 글을 공유하기