.메시지 전달하기

ToolTip 예제는 간단하기는 해도 아주 동작하는데 예제를 실행해 놓고 동작을 살펴보면서 툴팁 컨트롤이 어떻게 위에서 툴팁을 출력하는지 상상해 보자. 툴팁 컨트롤은 윈도우의 핸들, 사각 영역의 좌표에 대한 정보를 가지고 있으며 항상 마우스 위치를 감시하고 있다가 위에서 일정 시간을 머무르면 팝업 윈도우를 적당한 위치에 열어 툴팁을 보여 준다. 마우스 버튼을 누르거나 커서가 영역을 떠나면 툴팁은 즉시 사라지는데 이런 감시와 툴팁 출력 과정을 프로그램이 종료될 때까지 반복하고 있는 것이다.

누구나 상상할 있는 아주 간단한 원리대로 동작하고 있는데 이런 당연한 동작 체계에도 아주 곤란한 문제가 하나 있다. 그것은 툴팁 컨트롤이 어떻게 마우스 위치를 항상 감시할 있는가 하는 점이다. 마우스 커서가 움직이면 WM_MOUSEMOVE 메시지가 전달되는데 메시지는 커서를 캡처하지 않는 커서 바로 아래에 있는 윈도우에만 전달된다. 심지어 커서가 자식 윈도우 위를 움직일 때도 부모 윈도우는 사실을 알지 못한다.

하물며 일개 컨트롤에 불과하고 항상 숨겨져 있는 툴팁 윈도우가 WM_MOUSEMOVE 메시지를 받을 있겠는가? 메시지를 받지 못하면 툴팁 컨트롤은 커서 위치를 감시할 없으며 따라서 툴팁을 출력하는 것도 불가능하다. 나머지 마우스 메시지도 제대로 전달받아야 언제 툴팁을 숨길 것인가를 결정할 있다. 툴팁 컨트롤이 언제 툴팁을 출력할 것인가를 판단하기 위해서는 무슨 방법을 쓰던지 마우스의 이동을 항상 감시하고 있어야 하는데 여러 가지 방법을 생각해 있다.

 

우선 가장 무식한 방법으로 툴팁 컨트롤이 타이머를 설치해 놓고 주기마다 커서 위치를 감시하는 것이다. 가능한 방법이기는 하지만 실행 속도의 감소가 불가피하므로 좋은 방법은 아니다.

메시지를 훅킹하여 마우스 메시지를 무조건 툴팁 컨트롤이 먼저 받도록 한다. 훅킹은 무척 위험한 기법이므로 이것도 쉽지 않으며 부작용의 위험도 있다. ActiveX 컨트롤 다른 대안이 없는 경우에는 실제로 방법이 사용되기도 한다.

메시지 루프를 변경하여 마우스 관련 메시지를 툴팁 컨트롤로 보내준다. 구현은 쉽지만 메시지 루프에 변경을 가하는 것은 툴팁 컨트롤이 없으며 프로그래머가 직접 주어야 하므로 부담스럽다.

등록된 모든 툴들이 마우스 메시지를 받을 때마다 툴팁 컨트롤에게 메시지를 전달해 주어 툴팁에게 마우스 커서 위치를 확인할 기회를 준다.

 

이외에도 여러가지 방법을 생각해 있는데 이중 툴팁 컨트롤이 채택하고 있는 방법은 바로 ④번 방법이다. 윈도우 툴인 경우 그 자신, 사각 영역 툴인 경우 그 부모 윈도우를 서브클래싱하여 이 윈도우로 전달되는 마우스 메시지를 가로채면 항상 마우스 커서를 감시할 수 있다. 물론 메시지를 잠시 훔쳐 보기만 할 뿐 모든 메시지는 온전히 해당 툴로 수정없이 전달되어 툴이 고유의 처리를 할 수 있도록 해 주어야 한다.

이 방법을 사용하면 메시지 루프를 손댈 필요도 없고 각 툴도 원래 방식대로 동작하며 툴팁이 모든 처리를 내부에서 처리하므로 아주 깔끔하다. 툴을 등록할 때 윈도우 핸들을 전달하므로 툴팁 컨트롤이 툴을 서브클래싱하는데도 별 문제가 없다. ToolTip 예제의 두 툴은 다음 그림처럼 툴팁 컨트롤에 의해 서브클래싱된다.

툴팁으로 하여금 등록된 툴을 서브 클래싱하라는 명령이 바로 TTF_SUBCLASS 플래그이며 툴팁 컨트롤은 TTM_ADDTOOL 메시지로 툴을 등록할 플래그가 있으면 해당 툴을 알아서 서브 클래싱한다. 이때 서브 클래싱 대상은 윈도우 툴인 경우 해당 윈도우를, 사각 영역 툴인 경우 부모 윈도우가 된다. 툴을 서브 클래싱했으므로 툴로 전달되는 모든 메시지는 툴팁 컨트롤이 등록한 서브 클래스 프로시저가 먼저 받게 되며 따라서 툴팁 컨트롤은 마우스 위치를 항상 감시할 있다.

ToolTip 예제에서 TTF_SUBCLASS 플래그를 삭제하면 툴팁은 제대로 나타나지 않는다. 툴팁 컨트롤이 마우스 메시지를 받지 못하면 봉사가 되어 버려 언제 툴팁을 출력해야 하는지 전혀 판단할 없기 때문이다. 그래서 툴을 등록할 TTF_SUBCLASS 플래그를 주어 툴팁이 알아서 마우스 메시지를 가로채도록 허가하는 것이 좋다. 그렇다면 여기서 다른 의문이 생긴다. 서브클래싱이 문제가 없고 간편하다면 항상 서브클래싱을 하도록 하지 TTF_SUBCLASS 플래그를 별도로 지정해 주어야 하는가?

문제는 서브클래싱이 항상 가능한 것이 아니라는 점에 있다. 서브클래싱은 같은 스레드에 속한 윈도우에만 있으며 시스템 윈도우는 서브클래싱 대상이 없다. 이런 경우는 툴팁에게 마우스 메시지를 다른 방법으로 보내 주어야 하는데 앞에서 생각했던 여러 방안 하나를 선택하거나 아니면 다른 기발한 방법을 생각할 수도 있을 것이다.

③번 메시지 루프를 변경하는 방법이 비교적 쉬우므로 이 방법대로 툴팁 컨트롤을 만들어 보도록 하자. RelayEvent라는 이름으로 예제를 만들고 ToolTip 예제 소스를 복사해 온다. 그리고 TTF_SUBCLASS 플래그를 삭제한 후 실행해 보자. 과연 툴팁은 제대로 나타나지 않을 것이다. 이 예제의 메시지 루프를 다음과 같이 변경해 보자.

 

while(GetMessage(&Message,0,0,0)) {

    if (Message.message >= WM_MOUSEFIRST && Message.message <= WM_MOUSELAST)

       SendMessage(hTip,TTM_RELAYEVENT,0,(LPARAM)&Message);

    TranslateMessage(&Message);

    DispatchMessage(&Message);

}

 

메시지 큐에서 꺼낸 메시지가 마우스 관련 메시지일 경우는 메시지를 툴팁 컨트롤에게 전달해 주는데 이때는 TTM_RELAYEVENT 메시지를 보내 주면 된다. lParam 메시지 관련 정보인 MSG 구조체의 포인터를 전달해 주면 툴팁 컨트롤은 전달된 메시지를 분석하여 툴팁 컨트롤을 출력할 것이다. 이제 예제를 실행해 보면 제대로 툴팁이 출력된다.

서브클래싱을 하든지 아니면 메시지 루프를 변경하든지 어떤 방법으로든 마우스 메시지만 툴팁 컨트롤에게 전달해 주면 되므로 ToolTip 예제나 RelayEvent 예제나 실행 결과는 동일하다. 하지만 TTF_SUBCLASS 플래그를 지정하여 툴팁 컨트롤이 알아서 서브클래싱을 하도록 하는 방법이 훨씬 편하고 권장되는 방법이다. TTM_RELAYEVENT 메시지는 서브클래싱이 어려운 경우나 특수한 경우에 사용하면 된다.

참고로 MFC CToolTipCtrl 컨트롤이나 CToolBar 컨트롤은 몇가지 커스텀 처리를 하기 위해 서브클래싱 방법을 사용하지 않으며 TTM_RELAYEVENT 메시지로 툴팁을 구현하고 있다.