Tutorial 9: Child Window Controls
이 번장에서는 아주 중요한 부분 중 하나인, 차일드 윈도우 컨트롤에 대해서 설명한다.
소스 | 리소스 | 실행 결과 |
|
Windows에는 프로그래머가 따로 생성하지 않고 곧바로 사용할 수 있는 윈도우 클래스가 몇가지 미리 정의되어 있다. 대부분의 경우, 그들은 다이얼로그 박스의 요소로 사용되므로,차일드 윈도우 컨트롤(이하 「컨트롤」)로 불리어 지고 있다. 컨트롤은, 자신에게 보내진 마우스나 키보드 메세지를 직접 처리하고, 컨트롤의 상태가 변하면, 부모윈도우에게 통지신호를 보내게 되어 있어서, 프로그래머에게 아주 편리하게 사용할 수 있는 존재므로, 사용법을 반드시 익혀두어야한다. 실제로 대부분은 다이얼로그 박스에서 사용하게 되지만, 이 번장에서는 단순한 예제로서 표준 윈도우에서 컨트롤을 사용한다.
컨트롤을 사용하기 위해서는, CreateWindow 함수 혹은 CreateWindowEx 함수를 사용해서 생성해야 한다. 이미 이런 컨트롤들은 이미 Windows가 등록했으므로, 프로그래머가 다시 등록할 필요는 없다. 클래스명은, Windows에 정의되어 있는 클래스명을 그대로 사용해야 한다. 즉, 버튼을 사용하고 싶다면, CreateWindowEx 함수의 클래스이름에 "button" 과 같이 입력해야 한다. 다른 파라미터는, 부모윈도우의 핸들과 컨트롤을 구별하게 해주는 컨트롤 ID다. 컨트롤 ID는 유일해야하며, 이는 여러가지 많은 컨트롤중에서 해당 컨트롤을 구별할 수 있도록 한다.
컨트롤을 생성하고, 해당 컨트롤의 상태가 변경되면 부모윈도우로 통지메세지가 전송된다. 보통, 차일드 윈도우를 생성하는 시기는 WM_CREATE 메세지가 보내질때 생성하게 된다. 또한, 차일드 윈도우는 부모윈도우에게 WM_COMMAND 메세지를 보내고, wParam의 하위 워드에 자신의 컨트롤 ID가, 상위 워드에는 통지코드(notification code)가 들어있고, lParam는 윈도우 핸들이 저장되어 있다. 각 컨트롤은, 각각의 전용의 통지코드가 존재하며, 자세한것은 Win32API 레퍼런스를 참고하기 바란다.
부모윈도우는 SendMessage 함수를 사용해서 차일드 윈도우에 메세지를 보낼 수 있다.(주요통신 수단이다) SendMessage 함수는 윈도우 핸들을 가지는 윈도우에 대해서 메세지를 전송함과 동시에, wParam, lParam를 사용해서 어떠한 값이라도 넘겨줄 수있다. 이 함수는 아주 유용한 함수로서 , 윈도우 핸들만 알고 있으면 어떠한 윈도우에도 메세지를 보낼 수 있다.
결국 부모윈도우는, 차일드 윈도우를 생성했다면, 차일드 윈도우의 통지코드를 받기 위해서, WM_COMMAND 메세지를 처리해야 한다.
|
이 예제에서는 에디트 컨트롤과 푸쉬 버튼이 있는 윈도우를 생성한다. 버튼을 클릭하면, 에디트 박스에 입력된 문자열을 메시지 박스로 표시한다. 메뉴도 있고, 4개의 메뉴도 사용하고있다.
- Say Hello
에디트 박스에 문자열을 표시한다- Clear Edit Box
에디트 박스의 문자열을 클리어 한다- Get Text
에디트 박스에 있는 문자열을 메시지박스로 표시한다- Exit
종료한다
|
|
그럼 자세히 살펴보자.
.ELSEIF uMsg==WM_CREATE invoke CreateWindowEx, WS_EX_CLIENTEDGE, \ ADDR EditClassName, NULL,\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\ or ES_AUTOHSCROLL,\ 50,35,200,25, hWnd, EditID, hInstance, NULL mov hwndEdit, eax invoke SetFocus, hwndEdit invoke CreateWindowEx, NULL, ADDR ButtonClassName,\ ADDR ButtonText,\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\ 75,70,140,25, hWnd, ButtonID, hInstance, NULL mov hwndButton, eaxWM_CREATE 메세지에서 컨트롤을 생성하고 있다. 확장윈도우 스타일로 WS_EX_CLIENTEDGE 스타일을 지정해서 CreateWindowEx 함수를 호출 하면, 작업영역이 약간 들어간 형태의 윈도우가 된다. 컨트롤이름은 이미 정의되어 있는 것으로,"edit" 는 에디트 컨트롤, "button" 는 버튼 컨트롤이 된다. 기본 컨트롤이므로 이름을 변경할 수 없다.차일드 윈도우의 스타일을 결정해야하지만, 각 컨트롤은 보통의 윈도우 스타일에 추가적으로 확장스타일을 지정할 수도 있다. 예를 들면, 버튼 스타일이라면,"button style" 의 약자인,"BS_???" 와 같은 접두어가 붙게되고,에디트 스타일이라면,"edit style" 의 약자인 "ES_???" 라는 접두어가 붙은 스타일을 지정할 수 있다. 한가지 주의할 점은, 메뉴 핸들을 컨트롤 ID가 사용하는 점이다. 이것은,컨트롤은 주로 메뉴를 사용하지 않기 때문에, 아무런 문제가 없다. 그리고 컨트롤의 구별자로서 작동하게 된다.
컨트롤의 생성이 끝나면, 각각의 핸들을 저장해두고, SetFocus 함수를 호출 해서 , 에디트 박스에 포커스를 이동시켜서 , 유저가 문자열을 입력할 수 있도록 한다. 또한가지 흥미로운 사실은 모든 컨트롤은 WM_COMMAND 메세지를 부모윈도우에 전송한다는 것이다.(자신의 상태를 표현한다 : 통지신호)
.ELSEIF uMsg==WM_COMMAND mov eax, wParam .IF lParam==0어떠한 메뉴가 선택되면 WM_COMMAND 메세지를 부모윈도우로 전송한다는 것을 기억하기 바란다.그런데, 어떻게 메뉴로부터 발생한 WM_COMMAND인지, 컨트롤로부터 발생한 WM_COMMAND인지를 구별하는 것이 궁금할 것이다. 이것의 해답은 다음과 같다.
Low word of wParam High word of wParam lParam Menu Menu ID 0 0 Control Control ID Notification code Child Window Handle lParam를 주의 해서 보기 바란다. 만약 0 이라면, WM_COMMAND 메세지는 메뉴로부터 발생한 것이다. 메뉴ID와 컨트롤ID가 동일한 것일 수도 있고,통지코드가 0 일 수도 있기 때문에, wParam로는 메뉴나 컨트롤을 구별할 때 사용할 수 없다.
.IF ax==IDM_HELLO invoke SetWindowText, hwndEdit, ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText, hwndEdit, NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText, hwndEdit, ADDR buffer, 512 invoke MessageBox, NULL, ADDR buffer, ADDR AppName, MB_OKSetWindowText 함수를 호출 해서 , 에디트 박스에 문자열을 설정한다. 문자열을 비워두면, 에디트 박스내의 문자열을 클리어 할 수 있다. SetWindowText 함수는 자주 사용되는 API 함수로서 , 윈도우의 캡션이나, 버튼의 문자열을 변경할 수도 있다.
에디트 박스내의 문자열을 얻기 위해서는 GetWindowText 함수를 사용한다.
.IF ax==ButtonID shr eax, 16 .IF ax==BN_CLICKED invoke SendMessage, hWnd, WM_COMMAND, IDM_GETTEXT, 0 . ENDIF .ENDIF위의 코드는 유저가 버튼을 눌렀을 때의 조건문이다. 먼저, wParam의 하위워드가 컨트롤 ID인지를 체크한다. 만약 그렇다면, wParam의 상위워드(통지코드)가 버튼이 클릭되었을때 보내지는 BN_CLICKED인지를 체크한다.
통지코드가 BN_CLICKED라는 것이 흥미롭다. 에디트 박스로부터 문자열을 얻고, 메시지 박스로 그 문자열을 출력하고 싶지만, 이미 .ELSEIF ax==IDM_GETTEXT 의 부분에서 그 코드를 사용하고 있으므로, 여기서도 똑같이 처리하면 되지만, 조금 비효율적이다. 그래서, 같은처리를 하면서도 효율적으로 하기 위해서는 다음과 같은 방법을 생각할 수 있다. 간단히, .ELSEIF ax==IDM_GETTEXT 라고 하는 조건문에서 처리하면 되기 때문에 wParam의 하위 워드 값이 IDM_GETTEXT 가 되는 WM_COMMAND 메세지를 윈도우 프로시저에 전송 할 수 있으면 해결된다. 이것을 처리하는 것이,SendMessage 함수다. 이 함수는 어떤 윈도우라도 모든 메세지를 전송할 수 있으며, wParam, lParam에 여러가지 값을 설정할 수도 있다. 그렇기 때문에 같은 코드를 쓰는 일 없이, SendMessage 함수를 호출 하면 효율적으로 해결되게 된다. 이렇게 하면, 메뉴에서 "Get Text"메뉴를 선택한 것과 같은 효과를 가진다. 코드의 재사용성의 측면에서도 이와같은 기법은 효율적이다.
마지막으로 중요한 일이 한가지 있다. 메시지 루프에서 TraslateMessage 함수를 호출 하는 것을 잊어서는 안된다. 이유는 , 에디트 박스에서 문자열을 입력할 것이므로,해당 문자열을 문자코드로 변환해야 하는 것이다. 만약 이 함수를 호출하지 않으면, 에디트 박스에 아무것도 입력할 수 없게 될 것이다.