.콜백 항목

툴팁이 보여줄 텍스트는 툴을 등록할 TOOLINFO.lpszText 지정한다. , 처음 등록할 텍스트가 고정되며 툴바의 버튼이나 컨트롤에 대한 설명은 보통 미리 작성되어 있는 것이기 때문에 이렇게 해도 문제가 없다. 그러나 툴팁이 보여 주는 정보가 항상 고정된 설명인 것만은 아니며 프로그램의 현재 상황이나 선택한 부분에 대한 설명 또는 네트웍을 통해 들어오는 실시간 정보인 경우는 고정적이지 않다. 비주얼 C++ 디버깅 팁을 보여주는 툴팁의 경우 변수 위에서 변수의 현재값을 보여주는데 정보도 디버깅중에 수시로 변할 있는 값이며 툴을 등록할 미리 있는 것이 아니다.

이런 가변적인 정보를 툴팁으로 보여주기 위해서는 툴을 등록한 후에 텍스트를 변경할 있는 방법이 필요한데 이때 TTM_UPDATETIPTEXT라는 메시지를 사용한다. 메시지의 lParam으로 TOOLINFO 구조체를 전달하는데 cbSize 반드시 구조체 크기값으로 지정해 주어야 하며 uFlags, hwnd, uId 멤버로 대상 툴을 지정한다. 대상 툴이 윈도우이면 uFlags TTF_IDISHWND값을 주며 그렇지 않으면 0 값을 주면 된다. 툴을 등록하는 것이 아니므로 TTF_SUBCLASS 플래그는 필요가 없다.

lpszText 멤버에는 새로 변경할 문자열을 주는데 만약 텍스트가 문자열 리소스에서 읽어오는 것이라면 hinst에는 리소스를 가진 인스턴스 핸들을 전달해 주어야 하며 그렇지 않다면 hinst에는 NULL 대입해 준다. 메시지만 보내주면 즉시 텍스트가 변경되며 다음 툴팁이 나타날 변경된 텍스트가 나타난다. 메시지로 필요할 텍스트를 변경하거나 아니면 아예 텍스트를 콜백으로 등록할 수도 있다.

콜백 항목이란 툴팁이 텍스트를 출력하기 전에 부모 윈도우에게 통지 메시지를 보내 텍스트를 질문하고 결과를 출력하는 것이다. 콜백 항목은 등록할 lpszText 멤버에 LPSTR_TEXTCALLBACK 대입해 준다. 툴팁은 콜백 항목을 출력하기 직전에 TTN_GETDISPINFO 통지 메시지를 부모 윈도우로 보내 툴의 실제 텍스트를 물어본다. 툴팁도 공통 컨트롤이므로 통지 메시지는 WM_NOTIFY 메시지로 전달된다. 통지 메시지의 lParam에는 다음 구조체가 전달된다.

 

typedef struct tagNMTTDISPINFO {

    NMHDR      hdr;

    LPTSTR     lpszText;

    char       szText[80];

    HINSTANCE  hinst;

    UINT       uFlags;

#if (_WIN32_IE >= 0x0300)

    LPARAM     lParam;

#endif

} NMTTDISPINFO, FAR *LPNMTTDISPINFO;

 

uFlags, hdr.idFrom 멤버는 텍스트를 요청한 툴이 누구인가에 대한 정보를 가지며 uFlags TTF_IDISHWND 있으면 hdr.idFrom 윈도우 툴이며 그렇지 않으면 툴의 ID이다. 부모 윈도우는 어떤 툴에 대한 요청인가를 분석한 lpszText, szText, hinst 멤버에 결과 텍스트를 대입해 주면 된다. 텍스트가 80 이하인 경우 szText 버퍼에 텍스트를 복사해 주며 80 이상인 경우는 lpszText 문자열 포인터나 리소스의 ID 전달한다. 리소스 ID 경우 hinst 리소스를 가진 인스턴스의 핸들이다.

참고로 TTN_GETDISPINFO 통지 메시지는 TTN_NEEDTEXT라는 다른 이름으로도 정의되어 있으며 NMTTDISPINFO 구조체는 또한 TOOLIPTEXT라는 다른 이름으로 정의되어 있다. 공통 컨트롤의 메시지와 구조체 이름을 일관성있게 통합하기 위해 이름이 변경되었을 뿐이며 실제값은 동일하다. TTN_GETDISPINFO, NMTTDISPINFO라는 이름을 사용할 것을 권장하고 있지만 아직도 문서의 많은 부분에 과거의 이름이 사용되고 있으므로 이름들도 일단 알아두기 바란다. MFC 소스는 아직도 과거의 이름으로 작성되어 있다.

그럼 이제 실행중에 텍스트를 변경하는 예제를 작성해 보자. 가변적인 텍스트의 예는 여러 가지가 있겠지만 예제에서는 현재 시간값을 보여 주기로 한다. 물론 프로그램 상황에 따라 다른 형태의 정보를 보여주는 것도 얼마든지 가능하다. 세가지 방법으로 텍스트를 변경해 것이다.

 

#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

HDC hdc;

PAINTSTRUCT ps;

INITCOMMONCONTROLSEX iccex;

static HWND hTip, hBtn;

RECT rt1={300,50,500,150};

RECT rt2={550,50,750,150};

TOOLINFO ti;

SYSTEMTIME st;

char sTime[128];

LPNMHDR pNmHdr;

LPNMTTDISPINFO di;

 

switch(iMessage) {

case WM_CREATE:

    iccex.dwICC=ICC_WIN95_CLASSES;

    iccex.dwSize=sizeof(INITCOMMONCONTROLSEX);

    InitCommonControlsEx(&iccex);

 

    hTip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,NULL,0,

       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

       hWnd,NULL,g_hInst,NULL);

 

    hBtn=CreateWindow("button","버튼",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,

       50,50,200,100,hWnd,(HMENU)0,g_hInst,NULL);

 

    ti.cbSize=sizeof(TOOLINFO);

    ti.uFlags=TTF_SUBCLASS | TTF_IDISHWND;

    ti.hwnd=hWnd;

    ti.uId=(WPARAM)hBtn;

    ti.lpszText="empty";

    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)(LPTOOLINFO)&ti);

 

    ti.uFlags=TTF_SUBCLASS;

    ti.hwnd=hWnd;

    ti.uId=0;

    ti.lpszText="empty";

    ti.rect=rt1;

    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)(LPTOOLINFO)&ti);

 

    ti.uFlags=TTF_SUBCLASS;

    ti.hwnd=hWnd;

    ti.uId=1;

    ti.lpszText=LPSTR_TEXTCALLBACK;

    ti.rect=rt2;

    SendMessage(hTip,TTM_ADDTOOL,0,(LPARAM)(LPTOOLINFO)&ti);

 

    SetTimer(hWnd,0,1000,NULL);

    return 0;

case WM_TIMER:

    GetLocalTime(&st);

    wsprintf(sTime,"현재시간 = %d:%d:%d",st.wHour,st.wMinute,st.wSecond);

    ti.cbSize=sizeof(TOOLINFO);

    ti.hwnd=hWnd;

    ti.uFlags=TTF_IDISHWND;

    ti.uId=(WPARAM)hBtn;

    ti.hinst=NULL;

    ti.lpszText=sTime;

    SendMessage(hTip,TTM_UPDATETIPTEXT,0,(LPARAM)&ti);

    return 0;

case WM_NOTIFY:

    pNmHdr=(LPNMHDR)lParam;

    if (pNmHdr->hwndFrom == hTip) {

       switch (pNmHdr->code) {

       case TTN_SHOW:

          GetLocalTime(&st);

          wsprintf(sTime,"%d %d %d:%d:%d",st.wMonth, st.wDay,

             st.wHour,st.wMinute,st.wSecond);

          ti.cbSize=sizeof(TOOLINFO);

          ti.hwnd=hWnd;

          ti.uFlags=0;

          ti.uId=0;

          ti.hinst=NULL;

          ti.lpszText=sTime;

          SendMessage(hTip,TTM_UPDATETIPTEXT,0,(LPARAM)&ti);

          break;

       case TTN_GETDISPINFO:

          di=(LPNMTTDISPINFO)lParam;

          GetLocalTime(&st);

          wsprintf(sTime,"%d:%d:%d",st.wHour,st.wMinute,st.wSecond);

          // lstrcpy(di->szText,sTime);

          di->lpszText=sTime;

          di->hinst=NULL;

          break;

       }

    }

    return 0;

case WM_PAINT:

    hdc=BeginPaint(hWnd, &ps);

    Rectangle(hdc,rt1.left,rt1.top,rt1.right,rt1.bottom);

    SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));

    Rectangle(hdc,rt2.left,rt2.top,rt2.right,rt2.bottom);

    EndPaint(hWnd, &ps);

    return 0;

case WM_DESTROY:

    KillTimer(hWnd,0);

    PostQuitMessage(0);

    return 0;

}

return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

실행해 보자.

버튼과 개의 사각 영역이 있는데 위에 마우스를 올려 놓으면 현재 시간이 툴팁으로 출력될 것이다. 툴은 각각 다른 방법으로 실시간 텍스트를 구한다. 왼쪽 버튼부터 어떤 방법을 사용했는지 소스를 분석해 보도록 하자.

 

1.첫번째 방법은 타이머를 설치한 주기마다 TTM_UPDATETPTEXT 메시지로 텍스트를 강제 변경해 주는 방법이다. 텍스트가 변경될 시점에 바로 바로 텍스트도 같이 변경해 주는 방법인데 프로그램은 시간을 보여주기 때문에 타이머를 사용했지만 네트웍 정보라면 소켓 입력 시점에, 데이터 베이스의 값이라면 트리거에서 텍스트를 변경해 주어야 것이다. 강제로 텍스트를 변경하므로 심지어 툴팁이 출력되어 있는 상태에서도 시간이 계속 업데이트된다.

2.두번째 방법은 툴팁이 출력되기 직전인 TTN_SHOW 통지 메시지를 받았을 텍스트를 계산하는 방법이다. 통지 메시지는 툴팁이 화면으로 출력되기 직전에 보내지므로 이때 텍스트를 바꾸면 새로 대입한 텍스트가 출력된다. 텍스트를 변경할 때는 물론 TTM_UPDATETIPTEXT 메시지를 사용하였다. 출력되기 직전에 한번만 텍스트를 변경하므로 툴팁이 출력된 상태에서는 시간이 업데이트되지 않는다.

3.툴팁을 콜백 항목으로 등록하고 TTN_GETDISPINFO 통지 메시지를 받았을 텍스트를 조사해준다. 80 이하인 경우 szText 버퍼에, 80 이상인 경우 lpszText 멤버에 포인터를 대입해 주면 되는데 경우는 80 이하이므로 방법 모두 사용할 있다. 문자열 리소스도 물론 사용할 있다. 콜백 항목은 매번 출력하기 직전에 부모 윈도우에게 텍스트를 요구하는데 NMTTDISPINFO.uFlags TTF_DI_SETITEM값을 대입해 주면 이때 설정된 텍스트를 고정하고 다시 질문하지 않는다. 통지 메시지 처리 루틴에서 이상 텍스트가 변경되지 않을 같으면 플래그를 사용하면 된다.

 

텍스트를 실시간으로 변경하는 몇가지 방법을 보였는데 외에도 상황에 따라 적절한 방법을 사용할 있을 것이다.