2011년9월28일_winAPI_대화상자_컨트롤과의 통신


● 일반 컨트롤과 대화상자의 차일드 컨트롤의 차이점

에디트 컨트롤을 생성하는 코드는 아래와 같고,

   1: c1 = CreateWindow(TEXT("button"), TEXT("Draw Ellipse?"), WS_CHILD | WS_VISIBLE |
   2:     BS_AUTOCHECKBOX, 20, 20, 160, 25, hWnd, (HMENU)0, g_hInst, NULL);

9번째 인자로 컨트롤의 ID(여기서는 0)를 넣고 컨트롤이 생성이 되고 생성된 컨트롤을 다룰 수 있는 핸들이 반환된다.

그런데 대화상자를 생성하는 코드를 보면,

   1: // 리턴값은 어떤 버튼을 눌렀는지 확인. IDOK, IDCANCEL
   2: iResult = DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, AboutDlgProc);    //대화상자 생성

두 번째 인자로 다이얼로그의 ID를 넣고 대화상자를 생성한 뒤에,
EndDialog( )로 대화상자를 닫아야 블로킹이 풀리고 EndDialog()의 두 번째 인자가 전달되어 리턴된다.
여기서 잘 보면 대화상자 내부의 컨트롤들의 ID와 핸들은 관여하지 않는다는 것을 알 수 있다.
(대화상자는 컨트롤 종합선물세트(컨테이너)이니까 일일이 핸들을 반환하거나 인자로 넣기 곤란하겠지..)

대화상자 내부의 컨트롤들(차일드 컨트롤)의 값을 읽고 쓰려면 그 컨트롤의 핸들이 필요하다.
( malloc()로 할당받은 메모리를 엑세스하려면 가리키는 포인터가 필요하듯이 핸들이 필요함.)



● 차일드 컨트롤의 ID를 입력하여 핸들을 얻어내는 함수  GetDlgItem()

HWND GetDlgItem(HWND hDlg,              // 대화상자의 핸들  (소속)
                        int nlDDlgItem);         // 핸들을 얻고자 하는 컨트롤의 ID 

아래와 같이 리소스를 만들 때 대화상자내의 차일드 컨트롤들의 ID도 정할 수 있으니,

image 

엑세스 하고 (핸들을 얻고) 싶은 컨트롤의 ID를 두 번째 인자로 넣으면 그 컨트롤을 엑세스할 수 있는 핸들이 리턴된다.



● 차일드 컨트롤의 핸들을 입력하여 ID를 알아내는 함수  GetDlgCtrlID()

int GetDlgCtrlID(HWND hwndCtl);            // 컨트롤의 핸들 –> 컨트롤의 ID

이 함수는 좀 생뚱맞게 핸들로 ID를 알아낸다. 사용빈도가 별로 높지 않으니 있다는 것만 알아두자.


image

이런 변환은 어디서 많이 본 것 같은데 TCP/IP소켓 프로그래밍에서 32bit주소와 도메인, dotted주소 변환과 비슷하다.

왜 ID와 핸들을 따로 두어 프로그래머들을 귀찮게 하는가?
일단 컨트롤은 윈도우이며 따라서 윈도우를 관리하기 위해서는 핸들이 필요하다.
그런데 핸들이라는 것은 그 특성상 운영체제가 일방적으로 발급하는 것이기 때문에 번호의 연속성이 없으며,
그러다 보니 반복적인 처리에는 사용할 수 없다는 문제가 있다.

그래서 연속성을 가질 수 있고 사용자가 직접 번호를 지정할 수 있는 ID가 필요한 것이다.
ID는 연속적이라 for루프로 반복작업이 가능하고 CheckRadioButton( )로 라디오버튼의 그룹범위 지정도 가능하다.



● 대화상자 –> 차일드 컨트롤  메시지전송

image

대화상자가 차일드 컨트롤을 프로그래밍하는 주된 방법은 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( )는 컨트롤에 정수값을 대입하는 함수이다.



● 대화상자내의 에디트 컨트롤의 정수값을 기억하는 예제

   1: // --대화상자 메시지 처리--
   2: // WM_INITDIALOG메세지
   3: BOOL OnInitDialog(HWND hDlg, WPARAM wParam, LPARAM lParam)    //대화상자 생성시 초기화 위한 메시지
   4: {
   5:     // 9월 28일 수정.
   6:     SetDlgItemInt(hDlg, IDC_EDIT1, Age, FALSE);    //부호없는 정수값을 에디트 컨트롤로 보냄.
   7:  
   8:     return TRUE;
   9: }
  10:  
  11: // WM_COMMAND메시지
  12: BOOL OnDlgCommand(HWND hDlg, WPARAM wParam, LPARAM lParam)    //대화상자 컨트롤 메시지처리
  13: {    
  14:     switch(LOWORD(wParam)) {
  15:     case IDOK:
  16:         // 9월 28일 수정.
  17:         Age = GetDlgItemInt(hDlg, IDC_EDIT1, NULL, FALSE);    //에디트로부터 부호없는 정수값을 받아 리턴. 에러검사X
  18:         EndDialog(hDlg, IDOK);
  19:         return TRUE;
  20:     case IDCANCEL:
  21:         EndDialog(hDlg, IDCANCEL);    //CANCEL버튼 누르면 아무 일도 하지 않고 대화상자 소멸.
  22:         return TRUE;
  23:     }
  24:  
  25:     return TRUE;
  26: }

대화상자가 생성되면 WM_INITDIALOG메세지가 발생하여 OnInitDialog( )가 호출된다.

   6:     SetDlgItemInt(hDlg, IDC_EDIT1, Age, FALSE);    //부호없는 정수값을 에디트 컨트롤로 보냄.

SetDlgItemInt( )로 에디트박스(ID는 IDC_EDIT1)에 변수Age에 저장된 값을 부호를 없애고 기록한다.
(Age는 전역변수, DATA영역 –> heap영역)  
※ 초기화 되지 않은 전역변수는 BSS영역에 배치되나 DATA가 맘에 들어서! DATA영역.

  17:         Age = GetDlgItemInt(hDlg, IDC_EDIT1, NULL, FALSE);    //에디트로부터 부호없는 정수값을 받아 리턴. 에러검사X

GetDlgItemInt( )는 에디트박스(ID는 IDC_EDIT1)로부터 부호없는 정수값을 에러검사하지 않고 읽어와 리턴하여 Age에 저장한다.
(heap영역 –> DATA영역)

heap영역은 실행 중에 생성과 소멸을 반복한다. 대화상자가 생성되면 자리가 확보되고 대화상자가 소멸되면 모두 사라진다.
※ 실제 heap영역에 윈도우(컨트롤)의 데이터가 있는지 확인은 하지 않았으나 heap영역이외에 적재될 곳이 없다고 생각함.


<실행결과>

image image

어제 작성한 About예제코드에 상기의 코드들을 삽입하고 전역변수 Age를 선언하고 에디트리소스를 추가한 뒤에,
ID는 디폴트로 두고 실행하면 Age는 전역변수니 초기값이 0이다.
이 값이 마우스 왼쪽클릭하여 대화상자 생성 후 바로 기록되니 왼쪽 스크린샷과  같이 0이 출력된다.
에디트박스를 클릭하여 숫자를 300으로 수정하고 OK버튼을 클릭하여 대화상자를 닫으면,


image image

아무 것도 없는 공허한 부모윈도우가 다시 보이고, 이 상태로 종료하지 말고 차분하게 마우스왼쪽클릭하면,
대화상자가 생성되고 DATA영역에 저장된  



● 대화상자 <-> 컨트롤간 논리값 교환