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영역에 저장된  



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

상기 예제에서와 같이 에디트 컨트롤 엑세스는 문자열과 정수로 하고,
체크박스와 같이 체크가 되어 있나 아닌가의 상태만 있는 경우 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()와 비슷.

<실행결과>

imageimageimage

처음 실행하면 좌표(100, 100)에 “String”문자열만 달랑 출력되어 있는 윈도우가 보인다.
윈도우 내부에서 마우스 왼쪽 클릭하여 대화상자를 띄운 후 좌표와 문자열을 입력하고 OK버튼을 클릭하면,
대화상자에서 입력했던 좌표에 문자열이 출력된다.



<소스분석>

image

DATA, BSS, heap영역은 모두 컴퓨터 메인보드에 꽂힌 메모리에 존재하는 영역으로 상기의 그림과 같이 따로 나뉘어 있는 것이 아니다.
여기서 설명하고자 하는 것은 전연변수는 BSS영역에 위치하므로 윈도우가 존재하는 한 그 값을 계속 유지한다는 것이고,
대화상자와 자식윈도우인 컨트롤들은 모두 실행 중에 생성되므로 heap영역이니 값을 유지할 수 없다.
(핸들은 윈도우 생성시 받고 소멸 시 돌려준다.(제거))
그래서 설정값을 부모윈도우의 전역변수가 가지고 있게 하는 것이다. stack영역은 함수 리턴 시 제거되니 이 영역도 안 된다.
그리고 메모리를 선형으로 그리면 통신하는 기분이 안 드니 좌우에 배치해 예쁘게 했다.