2011년9월28일_winAPI_대화상자_컨트롤과의 통신
● 일반 컨트롤과 대화상자의 차일드 컨트롤의 차이점
에디트 컨트롤을 생성하는 코드는 아래와 같고,
9번째 인자로 컨트롤의 ID(여기서는 0)를 넣고 컨트롤이 생성이 되고 생성된 컨트롤을 다룰 수 있는 핸들이 반환된다.
그런데 대화상자를 생성하는 코드를 보면,
두 번째 인자로 다이얼로그의 ID를 넣고 대화상자를 생성한 뒤에,
EndDialog( )로 대화상자를 닫아야 블로킹이 풀리고 EndDialog()의 두 번째 인자가 전달되어 리턴된다.
여기서 잘 보면 대화상자 내부의 컨트롤들의 ID와 핸들은 관여하지 않는다는 것을 알 수 있다.
(대화상자는 컨트롤 종합선물세트(컨테이너)이니까 일일이 핸들을 반환하거나 인자로 넣기 곤란하겠지..)
대화상자 내부의 컨트롤들(차일드 컨트롤)의 값을 읽고 쓰려면 그 컨트롤의 핸들이 필요하다.
( malloc()로 할당받은 메모리를 엑세스하려면 가리키는 포인터가 필요하듯이 핸들이 필요함.)
● 차일드 컨트롤의 ID를 입력하여 핸들을 얻어내는 함수 GetDlgItem()
HWND GetDlgItem(HWND hDlg, // 대화상자의 핸들 (소속)
int nlDDlgItem); // 핸들을 얻고자 하는 컨트롤의 ID |
아래와 같이 리소스를 만들 때 대화상자내의 차일드 컨트롤들의 ID도 정할 수 있으니,
엑세스 하고 (핸들을 얻고) 싶은 컨트롤의 ID를 두 번째 인자로 넣으면 그 컨트롤을 엑세스할 수 있는 핸들이 리턴된다.
● 차일드 컨트롤의 핸들을 입력하여 ID를 알아내는 함수 GetDlgCtrlID()
int GetDlgCtrlID(HWND hwndCtl); // 컨트롤의 핸들 –> 컨트롤의 ID |
이 함수는 좀 생뚱맞게 핸들로 ID를 알아낸다. 사용빈도가 별로 높지 않으니 있다는 것만 알아두자.
이런 변환은 어디서 많이 본 것 같은데 TCP/IP소켓 프로그래밍에서 32bit주소와 도메인, dotted주소 변환과 비슷하다.
왜 ID와 핸들을 따로 두어 프로그래머들을 귀찮게 하는가?
일단 컨트롤은 윈도우이며 따라서 윈도우를 관리하기 위해서는 핸들이 필요하다.
그런데 핸들이라는 것은 그 특성상 운영체제가 일방적으로 발급하는 것이기 때문에 번호의 연속성이 없으며,
그러다 보니 반복적인 처리에는 사용할 수 없다는 문제가 있다.
그래서 연속성을 가질 수 있고 사용자가 직접 번호를 지정할 수 있는 ID가 필요한 것이다.
ID는 연속적이라 for루프로 반복작업이 가능하고 CheckRadioButton( )로 라디오버튼의 그룹범위 지정도 가능하다.
● 대화상자 –> 차일드 컨트롤 메시지전송
대화상자가 차일드 컨트롤을 프로그래밍하는 주된 방법은 SendMessage( )로 메시지를 보내는 것인데,
이 함수는 대상 윈도우의 핸들을 요구한다. SendMessage( )의 첫 번째 인자로 윈도우의 핸들을 넣는데 여기에 엑세스하고 싶은 차일드 컨트롤의 핸들을 넣는다. 그래서 GetDlgItem( )로 핸들을 알아내 SendMessage( )의 인자로 넘겨준다.
SendMessage(GetDlgItem(hDlg, 컨트롤의 ID),… ); |
이렇게 사용하면 번거로우니 아래와 같은 함수를 사용하여,
LONG SendDlgItemMessage(HWND hDlg, // 대화상자의 핸들
int nID, // 메시지를 받을 컨트롤의 ID UINT Msg, // 메시지 WPARAM wParam, // 파라미터 w (이건 그 때 그 때 달라요~) LPARAM lParam); // 파라미터 l (이건 그 때 그 때 달라요~) |
알고 있는 (resouce.h에 등록된) 차일드 컨트롤의 ID와 소속된 대화상자의 핸들을 사용하여 한 번에 해결한다.
이 함수는 GetDlgItem( )와 SendMessage( )를 호출하는 레퍼함수라고 할 수 있다.
(레퍼함수는 printf( )처럼 다른 함수를 호출하는 함수)
● 대화상자 <-> 컨트롤간 문자열 교환
UINT GetDlgItemText(HWND hDlg, // 대화상자 윈도우 핸들
int nID, // 엑세스할 컨트롤의 ID LPTSTR lpString, // 문자열을 담을 버퍼 int nMaxCoutn); // 문자열길이 |
GetDlgItemText( )는 컨트롤로부터 문자열을 읽어와 버퍼에 저장하는 함수이고,
fgets( )와 비슷하게 읽어 올 문자수를 지정할 수 있다.
BOOL SetDlgItemText(HWND hDlg, // 대화상자 윈도우 핸들
int nID, // 엑세스할 컨트롤의 ID LPCTSTR lpString); // 쓸 문자열 |
SetDlgItemText( )는 컨트롤에 문자열을 쓰는 함수이다.
● 대화상자 <-> 컨트롤간 정수 교환
UINT GetDlgItemInt(HWND hDlg, // 대화상자 윈도우 핸들
int nID, // 엑세스할 컨트롤의 ID BOOL *lpTranslated, // 에러 검사할 필요없으면 NULL BOOL bSigned); // TRUE = 부호있는 정수, FALSE = 부호없는 정수 리턴 |
GetDlgItemInt( )는 컨트롤로부터 정수값을 읽어와 리턴하는 함수이다.
변환의 성공 여부를 리턴받기 위한 포인터 변수. 정수로 변환하지 못할 문자열이면 이 변수로 FALSE가 리턴된다.
성공 여부를 조사할 필요가 없으면 NULL로 줄 수 있다. - 참조URL: http://winapi.co.kr
BOOL SetDlgItemInt(HWND hDlg, // 대화상자 윈도우 핸들
int nID, // 엑세스할 컨트롤의 ID UINT uValue, // 컨트롤에 대입할 정수값 BOOL bSigned); // TRUE = 부호있는 정수, FALSE = 부호없는 정수 리턴 |
SetDlgItemInt( )는 컨트롤에 정수값을 대입하는 함수이다.
● 대화상자내의 에디트 컨트롤의 정수값을 기억하는 예제
대화상자가 생성되면 WM_INITDIALOG메세지가 발생하여 OnInitDialog( )가 호출된다.
SetDlgItemInt( )로 에디트박스(ID는 IDC_EDIT1)에 변수Age에 저장된 값을 부호를 없애고 기록한다.
(Age는 전역변수, DATA영역 –> heap영역)
※ 초기화 되지 않은 전역변수는 BSS영역에 배치되나 DATA가 맘에 들어서! DATA영역.
GetDlgItemInt( )는 에디트박스(ID는 IDC_EDIT1)로부터 부호없는 정수값을 에러검사하지 않고 읽어와 리턴하여 Age에 저장한다.
(heap영역 –> DATA영역)
heap영역은 실행 중에 생성과 소멸을 반복한다. 대화상자가 생성되면 자리가 확보되고 대화상자가 소멸되면 모두 사라진다.
※ 실제 heap영역에 윈도우(컨트롤)의 데이터가 있는지 확인은 하지 않았으나 heap영역이외에 적재될 곳이 없다고 생각함.
<실행결과>
어제 작성한 About예제코드에 상기의 코드들을 삽입하고 전역변수 Age를 선언하고 에디트리소스를 추가한 뒤에,
ID는 디폴트로 두고 실행하면 Age는 전역변수니 초기값이 0이다.
이 값이 마우스 왼쪽클릭하여 대화상자 생성 후 바로 기록되니 왼쪽 스크린샷과 같이 0이 출력된다.
에디트박스를 클릭하여 숫자를 300으로 수정하고 OK버튼을 클릭하여 대화상자를 닫으면,
아무 것도 없는 공허한 부모윈도우가 다시 보이고, 이 상태로 종료하지 말고 차분하게 마우스왼쪽클릭하면,
대화상자가 생성되고 DATA영역에 저장된
● 대화상자 <-> 컨트롤간 논리값 교환
상기 예제에서와 같이 에디트 컨트롤 엑세스는 문자열과 정수로 하고,
체크박스와 같이 체크가 되어 있나 아닌가의 상태만 있는 경우 BOOL형을 사용한다.
BOOL CheckDlgButton(HWND hDlg, // 부모 원도우 핸들 (대화상자)
int nIDButton, // 버튼의 ID (체크박스는 “button”클래스) UINT nCheck); // 원하는 체크 상태 |
CheckDlgButton( )는 체크박스나 라디오 버튼 등 체크 상태를 가지는 버튼의 체크 상태를 변경한다.
이 때 해당 컨트롤로 BM_SETCHECK 메시지를 보낸다고 하는데 이는 우리가 처리할 필요 없으니 몰라도 된다.
세 번째 인자로 체크 상태를 지정하며 아래의 세 값 중 하나를 지정한다.
nCheck값 | 설명 |
BST_CHECKED | 체크 상태로 바꿈 |
BST_UNCHECKED | 체크 상태를 해제 |
BST_INDETERMINATE | 체크 상태를 알 수 없는 grayed로 체크.
BS_3STATE, BS_AUTO3STATE 스타일을가지는 체크박스에만 사용해야함 |
UINT IsDlgButtonChecked(HWND hDlg, // 부모 윈도우 핸들 (대화상자) int nIDButton); // 버튼의 ID (체크박스는 “button”클래스) |
IsDlgButtonChecked( )는 체크박스의 상태를 검사하여 체크상태면 TRUE를 리턴하고,
체크해제상태면 FALSE를 리턴한다.
리턴값은 상기의 CheckDlgButton( )의 nCheck값 표를 참조하면 된다.
● 부모윈도우와 대화상자간 데이터 전달 예제: InfoDlg
▷ main.cpp (시작 + CALLBACK함수 정의)
1: // winAPI p.242~244 InfoDlg
2: // 2011년 9월 28일 작성
3: // 작성자: 김수만
4:
5: #include <windows.h> // 모든 API함수들의 원형과 사용하는 상수들이 정의되어 있음.
6: #include "MsgProc.h" //add
7: #include "resource.h"
8:
9: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //메시지처리함수
10: HINSTANCE g_hInst; //핸들 인스턴스
11: LPCTSTR lpszClass = TEXT("InfoDlg"); //클래스명
12:
13: //add
14: typedef struct MESSAGEMAP {
15: UINT iMessage;
16: LRESULT (*lpfnMsgProc)(HWND, WPARAM, LPARAM);
17: } MESSAGEMAP;
18:
19: typedef struct DLGMESSAGEMAP {
20: UINT iMessage;
21: BOOL (*lpfnMsgProc)(HWND, WPARAM, LPARAM);
22: } DLGMESSAGEMAP;
23:
24:
25: //시작함수.
26: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
27: {
28: HWND hWnd;
29: MSG Message;
30: WNDCLASS WndClass;
31: g_hInst = hInstance;
32:
33: WndClass.cbClsExtra = 0;
34: WndClass.cbWndExtra = 0;
35: WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
36: WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
37: WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
38: WndClass.hInstance = hInstance;
39: WndClass.lpfnWndProc = WndProc;
40: WndClass.lpszClassName = lpszClass;
41: WndClass.lpszMenuName = NULL;
42: WndClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
43: RegisterClass(&WndClass);
44:
45:
46: hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
47: CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
48: NULL, (HMENU)NULL, hInstance, NULL);
49:
50: ShowWindow(hWnd, nCmdShow);
51:
52: while(GetMessage(&Message, NULL, 0, 0)) {
53: TranslateMessage(&Message);
54: DispatchMessage(&Message);
55: }
56:
57: return (int)Message.wParam;
58: }
59:
60: //운영체제가 호출하는 CALLBACK함수 (메시지 처리)
61: LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
62: {
63: //add
64: int i;
65:
66: static MESSAGEMAP MessageMaps[] = {
67: {WM_CREATE, OnCreate},
68: {WM_COMMAND, OnCommand},
69: {WM_HSCROLL, OnHScroll},
70: {WM_LBUTTONDOWN, OnLButtonDown},
71: {WM_RBUTTONDOWN, OnRButtonDown},
72: {WM_MOUSEMOVE, OnMouseMove},
73: {WM_LBUTTONUP, OnLButtonUp},
74: {WM_LBUTTONDBLCLK, OnLButtonDBLCLK},
75: {WM_PAINT, OnPaint},
76: {WM_DESTROY, OnDestroy}
77: };
78:
79: for(i = 0 ; i < sizeof(MessageMaps) / sizeof(MessageMaps[0]) ; ++i)
80: {
81: if(MessageMaps[i].iMessage == iMessage)
82: {
83: return (*MessageMaps[i].lpfnMsgProc)(hWnd, wParam, lParam);
84: }
85: }
86: /////////////////////////////////
87:
88:
89:
90: return(DefWindowProc(hWnd, iMessage, wParam, lParam));
91: }
92:
93:
94: BOOL CALLBACK InfoDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
95: {
96: // 20110927 add
97: int i;
98:
99: static DLGMESSAGEMAP DMessageMaps[] = {
100: //메시지 추가할 것.
101: {WM_INITDIALOG, OnInitDialog},
102: {WM_COMMAND, OnDlgCommand}
103: };
104:
105: for(i = 0 ; i < sizeof(DMessageMaps) / sizeof(DMessageMaps[0]) ; ++i)
106: {
107: if(DMessageMaps[i].iMessage == iMessage)
108: {
109: return (*DMessageMaps[i].lpfnMsgProc)(hDlg, wParam, lParam);
110: }
111: }
112:
113: return FALSE; //정의되어 있지 않은 메시지는 모두 FALSE, 정의된건 TRUE
114: }
▷ MsgProc.cpp (메시지 처리)
1: //MsgProc.cpp
2:
3: #include "MsgProc.h"
4: #include "resource.h"
5:
6: int x;
7: int y;
8: TCHAR str[128];
9: HWND hWndMain; //이건 어디서 쓰지?
10:
11: LRESULT OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
12: {
13: hWndMain = hWnd; //모르겠다.
14: x = 100; //초기x좌표
15: y = 100; //초기y좌표
16: lstrcpy(str, TEXT("String")); //str배열에 초기값으로 "String"문자열 복사.
17:
18: return 0;
19: }
20:
21: LRESULT OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
22: {
23: return 0;
24: }
25:
26: LRESULT OnHScroll(HWND hWnd, WPARAM wParam, LPARAM lParam) //수평스크롤 변화시 발생하는 메시지처리
27: {
28: return 0;
29: }
30:
31: LRESULT OnLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
32: {
33: if(DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG1), // IDD_DIALOG1 리소스를 사용하여 대화상자생성
34: hWnd, InfoDlgProc) == IDOK) // OK버튼을 눌렀다면
35: {
36: InvalidateRect(hWnd, NULL, TRUE); //부모윈도우 전체영역을 무효화함.
37: }
38:
39: return 0;
40: }
41:
42: LRESULT OnRButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
43: {
44: return 0;
45: }
46:
47:
48: LRESULT OnMouseMove(HWND hWnd, WPARAM wParam, LPARAM lParam)
49: {
50:
51: return 0;
52: }
53:
54: LRESULT OnLButtonUp(HWND hWnd, WPARAM wParam, LPARAM lParam)
55: {
56: return 0;
57: }
58:
59: LRESULT OnLButtonDBLCLK(HWND hWnd, WPARAM wParam, LPARAM lParam)
60: {
61:
62: return 0;
63: }
64:
65:
66: LRESULT OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
67: {
68: HDC hdc;
69: PAINTSTRUCT ps;
70:
71: hdc = BeginPaint(hWnd, &ps); //DC얻기
72:
73: TextOut(hdc, x, y, str, lstrlen(str)); // 에디트에 넣은 좌표와 문자열을 표시
74:
75: EndPaint(hWnd, &ps);
76:
77: return 0;
78: }
79:
80: LRESULT OnDestroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
81: {
82: PostQuitMessage(0);
83: return 0;
84: }
85:
86:
87: //////////////////////////////////////////////////////////////////////////////////////
88:
89: BOOL OnInitDialog(HWND hDlg, WPARAM wParam, LPARAM lParam)
90: { //대화상자 초기화시
91: SetDlgItemText(hDlg, IDC_STR, str); //IDC_STR에디트는 str배열에 저장된 문자열로 초기화
92: SetDlgItemInt(hDlg, IDC_X, x, FALSE); //IDC_X에디트는 x좌표표시
93: SetDlgItemInt(hDlg, IDC_Y, y, FALSE); //IDC_Y에디트는 y좌표표시
94:
95: return TRUE; //메시지를 처리했으니 TRUE리턴
96: }
97:
98: BOOL OnDlgCommand(HWND hDlg, WPARAM wParam, LPARAM lParam)
99: { // 대화상자내의 메시지 발생시
100: switch(LOWORD(wParam)) { // 통지메시지를 검사해
101: case IDOK: // OK버튼을 눌렀다면
102: GetDlgItemText(hDlg, IDC_STR, str, 128); //IDC_STR에디트로부터 문자열 읽어와 str배열에 저장
103: x = GetDlgItemInt(hDlg, IDC_X, NULL, FALSE); //IDC_X에디트로부터 x좌표 읽어와 리턴
104: y = GetDlgItemInt(hDlg, IDC_Y, NULL, FALSE); //IDC_Y에디트로부터 y좌표 읽어와 리턴
105: EndDialog(hDlg, IDOK); //대화상자를 소멸시키고 DialogBox()의 리턴값은 IDOK
106: return TRUE; //메시지를 처리했으니 TRUE리턴
107:
108: case IDCANCEL: //Cancel버튼을 눌렀다면
109: EndDialog(hDlg, IDCANCEL); //대화상자를 소멸시키고 DialogBox()의 리턴값은 IDCANCEL
110: return TRUE; //메시지를 처리했으니 TRUE리턴
111: }
112:
113: return FALSE; //정의된 메시지처리가 없으면 FALSE를 리턴함.
114: } //DefWindowProc()와 비슷.
<실행결과>
처음 실행하면 좌표(100, 100)에 “String”문자열만 달랑 출력되어 있는 윈도우가 보인다.
윈도우 내부에서 마우스 왼쪽 클릭하여 대화상자를 띄운 후 좌표와 문자열을 입력하고 OK버튼을 클릭하면,
대화상자에서 입력했던 좌표에 문자열이 출력된다.
<소스분석>
DATA, BSS, heap영역은 모두 컴퓨터 메인보드에 꽂힌 메모리에 존재하는 영역으로 상기의 그림과 같이 따로 나뉘어 있는 것이 아니다.
여기서 설명하고자 하는 것은 전연변수는 BSS영역에 위치하므로 윈도우가 존재하는 한 그 값을 계속 유지한다는 것이고,
대화상자와 자식윈도우인 컨트롤들은 모두 실행 중에 생성되므로 heap영역이니 값을 유지할 수 없다.
(핸들은 윈도우 생성시 받고 소멸 시 돌려준다.(제거))
그래서 설정값을 부모윈도우의 전역변수가 가지고 있게 하는 것이다. stack영역은 함수 리턴 시 제거되니 이 영역도 안 된다.
그리고 메모리를 선형으로 그리면 통신하는 기분이 안 드니 좌우에 배치해 예쁘게 했다.