Windows GUI 프로그램은 잘 알다시피 메시지로 운영체제와 의사소통을 하는 게 핵심이다. “이 창은 응답이 없음. 프로그램을 강제 종료할까요?”라고 판단을 하는 기준은 바로 n초 이상 GetMessage/PeekMessage 함수를 호출하지 않는 프로그램이다.

CreateWindowEx로 창을 하나 만든 뒤 메시지 loop은 GetMessage로 메시지를 받아서 그걸 DispatchMessage로 넘겨 주기를 반복하는 형태이다.
비록 메시지를 윈도우 프로시저로 전달하는 함수로는 CallWindowProc도 있겠지만, Dispatch는 타이머 메시지를 윈도우 프로시저 대신 lParam에 지정된 타이머 프로시저로 직통 전달하는 일까지 한다. 즉, 하는 일의 범위가 더 넓다. 그리고 Call은 메시지 큐를 처리하는 게 아니라 윈도우 프로시저의 서브클래싱용으로 쓰라고 있는 물건이다.

메시지를 Dispatch하기 전에 보통은 translation이라고 불리는 전처리 과정이 필요하다. TranslateMessage 함수가 그 일을 하는데, 키보드 WM_(SYS)KEYDOWN / WM_(SYS)KEYUP 메시지로부터 WM_(SYS)CHAR 메시지를 추가로 생성해 준다. 현재 설정되어 있는 키보드 드라이버의 정보를 토대로 가상 키코드를 해석하여, 그 키가 나타내는 입력 문자를 알려 준다는 뜻이다. 유럽 문자를 입력할 때 쓰이는 dead key를 처리하는 것도 translator의 몫이다.

이때 새로 생성된 메시지는 이 윈도우를 구동하는 스레드의 메시지 큐에 추가로 예약되어서 다음번 GetMessage 함수 호출 때 걸려 나온다. 인자로 전달된 MSG 구조체의 내용이 지금 당장 바뀌지는 않는다. 사실, 문자를 입력받는 윈도우가 아니라면 translation은 꼭 필요하지는 않은지도 모르겠다.

그런데 메시지를 굳이 윈도우 프로시저로 dispatch하기 전에, 응용 프로그램이나 다른 API 함수가 곧장 메시지를 가로채야 하는 경우는 굉장히 많이 있다. 액셀러레이터 단축키를 처리하는 TranslateAccelerator, 그리고 Ctrl+F6 같은 MDI 공용 단축키를 처리하는 TranslateMDISysAccel이 그 예이다.
TranslateMessage는 그런 관문들을 모두 통과한 메시지가 dispatch 직전에 최종적으로 거치는 관문일 뿐이다.

이런 절차가 한둘이 아니기 때문에 MFC의 CWnd 클래스는 그런 코드를 적절히 넣으라고 PreTranslateMessage라는 가상 함수까지 마련해 놓았다. 이 함수에서 키보드 메시지를 조작하여 엔터가 눌려도 대화상자가 종료되지 않게 하는 것 정도는 프로그래밍 초보 시절에 다들 해 보았을 것이다. 그러고 보니 이렇게 dispatch 전단계에서 조작하는 메시지는 다들 키보드 관련 메시지인 경우가 많다.

그리고 이런 식으로 전용 함수를 통해 미리 처리되는 또 다른 대표적인 메시지들은 바로.. 대화상자 관련 메시지들이다.
이걸 담당하는 물건으로는 IsDialogMessage라는 함수가 있다.

물론, MFC의 경우 저 함수를 포함해 위에서 언급한 각종 전처리, translation을 이미 알아서 전부 호출하며 메시지 loop과 심지어 WinMain 함수까지 몽땅 라이브러리 내부에다 짱박아 뒀기 때문에 MFC를 사용하는 프로그램이라면 자신이 직접 그런 함수를 또 호출해야 할 일은 거의 없을 것이다.
그러나 운영체제와 응용 프로그램 사이에 어떤 메커니즘으로 통신이 오가는지 알고는 있을 필요가 있다.

대화상자 안에서 사용자가 tab 키를 눌렀을 때 컨트롤들의 포커스가 바뀌는 것, Alt+알파벳을 눌렀을 때 특정 컨트롤로 바로 이동하는 것, 엔터를 눌렀을 때 '확인' 종료가 되고 ESC를 눌렀을 때 '취소' 종료가 되는 것 따위는 대화상자의 윈도우 프로시저의 관할이 아니다. 윈도우 프로시저가 메시지를 받기 전에 저 IsDialogMessage 함수가 일찌감치 처리한다. 심지어 default 버튼의 테두리를 굵게 칠하는 것조차도 저 함수가 담당한다.
물론 그런 전처리는 대화상자가 독단적으로 하는 게 아니라 자기 아래의 컨트롤에게 WM_GETDLGCODE 메시지를 보내서 컨트롤이 원하는 키보드 처리 양상에 대해서 물어는 보고서 한다.

DialogBox 함수로 modal 대화상자를 만들었다면야 그 함수가 자체적으로 메시지 loop을 돌면서 이런 처리를 모두 알아서 해 준다. 그러나 문제는 modeless 대화상자이다. modeless 대화상자는 그 창을 만든 응용 프로그램의 main 메시지 loop를 같이 쓰면서 돌아가기 때문에 그 loop에서 IsDialogMessage 함수를 호출하는 로직이 추가되어야 한다.

대화상자에서 엔터 키가 들어왔을 때의 동작에 대해서는 살짝 주의할 점이 있다.
일반 버튼은 자체적으로 엔터 키를 처리한다. 그래서 다른 모든 컨트롤들을 사용 중일 때 엔터를 누르면 '확인' 버튼이 눌러진 것으로 처리되고 대화상자가 곧장 종료되지만, '취소'나 '적용' 같은 다른 버튼에 키보드 포커스가 가 있을 때 엔터를 누르면 해당 버튼이 눌러진 것으로 처리된다.

즉, 엔터는 space와 사실상 동일하게 처리된다. 차이가 있다면 space는 key down 시점 때는 버튼이 눌러진 모습만 표시되고 key up 시점일 때 명령이 먹히는 반면, 엔터는 key down 때 곧바로 먹힌다는 것이 전부이다.
다만, 대화상자 안에 또 대화상자가 자식 윈도우로 생성되었고 자식 대화상자 안에 있는 버튼을 space가 아닌 엔터 키로 눌렀을 경우,

WM_COMMAND+BN_CLICKED 메시지는 자식 대화상자의 윈도우 프로시저로 전달되는 게 아니라 겉의 최상위 대화상자의 윈도우 프로시저로 전달된다.
그래서 그 버튼을 엔터 키로 누른 동작은 일반적으로 제대로 인식되지 않거나 최악의 경우 오동작--부모 대화상자와 자식 대화상자에 우연히 ID값이 같은 버튼이 공존한다면!--까지 하게 된다.

왜 그렇게 동작하는지 정확한 이유는 난 모르겠다.
물론, 일반적으로 엔터를 눌러서 대화상자를 닫는 메시지 자체는 자식 대화상자가 아니라 최상위 대화상자에다가 전달되는 게 개념상 올바르다.
하지만 그런 default 버튼이 아니라 일반 버튼을 엔터 키로 누르는 동작까지 왜 엉뚱한 곳으로 가는 걸까?

대화상자 안에 또 대화상자를 만드는 건 운영체제가 제공하는 프로퍼티 시트가 대표적인 예인데 그건 운영체제가 알아서 버튼의 처리를 잘 해 주는 듯하다. <날개셋> 타자연습 같은 프로그램에서 탭 안에 있는 버튼을 엔터로 눌러 보면 잘 안식된다.

그러나 자체적으로 자식 대화상자들을 표시하는 <날개셋> 한글 입력기의 제어판, 그리고 VMware, 반디집, EditPlus 등의 유수의 소프트웨어들이 제공하는 종합 설정 대화상자들을 살펴보면, 자식 대화상자 안의 버튼은 space만 인식되지 엔터 키로 인식되지 않는 걸 알 수 있다. (아래 그림에서 왼쪽)
단, Microsoft Visual Studio는 예외. '도구-옵션' 대화상자의 내부에 표시된 자식 대화상자들의 버튼은 엔터 키로 잘 인식된다. (아래 그림에서 오른쪽) 치명적인 버그 같은 부류는 아니지만 운영체제의 특성 때문에 발생하는 좀 이상한 면모라 하겠다.

사용자 삽입 이미지사용자 삽입 이미지
WM_COMMAND+BN_CLICKED 메시지는 lParam으로 이 메시지를 보낸 컨트롤의 윈도우 핸들을 친절하게 일일이 알려 준다.
그렇기 때문에 그런 버튼을 누르는 걸 인식하려면, 최상위 대화상자의 윈도우 프로시저에서 lParam을 검사하여 이것이 자식 대화상자가 아니라 내 대화상자의 버튼이 보낸 메시지가 맞는지를 확인해야 한다. 아니라면 그 버튼의 진짜 부모 윈도우--GetParent 함수로 확인--에다가 메시지를 도로 보내 버리면 된다.

Visual Studio의 옵션 대화상자도 Spy++로 동작을 확인해 보면, WM_COMMAND+BN_CLICKED 메시지가 두 번 가는 걸 보니 그런 식으로 동작하는 듯하다.

Posted by 사무엘

2013/11/22 08:36 2013/11/22 08:36
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/901

Trackback URL : http://moogi.new21.org/tc/trackback/901

Leave a comment
« Previous : 1 : ... 1040 : 1041 : 1042 : 1043 : 1044 : 1045 : 1046 : 1047 : 1048 : ... 1840 : Next »

블로그 이미지

철도를 명절 때에나 떠오르는 4대 교통수단 중 하나로만 아는 것은, 예수님을 사대성인· 성인군자 중 하나로만 아는 것과 같다.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2022/01   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          

Site Stats

Total hits:
1730757
Today:
362
Yesterday:
564