Search code examples
c++winapiunicodetooltip

How to set up Win32 tooltips control with dynamic unicode text?


I am having some trouble provding a Win32 tooltips control with dynamic text in unicode format. I use the following code to set up the control:

INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);

HWND hwnd_tip = CreateWindowExW(0, TOOLTIPS_CLASSW, NULL, 
  WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, 
  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  NULL, NULL, hinst, NULL
);
SetWindowPos(hwnd_tip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

TOOLINFOW ti;
memset(&ti, 0, sizeof(TOOLINFOW));
ti.cbSize = sizeof(TOOLINFOW);
ti.hwnd = hwnd_main;
ti.uId = (UINT) hwnd_control;
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.lpszText = L"This tip is shown correctly, including unicode characters.";
SendMessageW(hwnd_tip, TTM_ADDTOOLW, 0, (LPARAM) &ti);

This works fine as long as I provide the tooltip text in ti.lpszText. However, I want the text to be dynamic, so instead I set ti.lpszText to LPSTR_TEXTCALLBACKW and handle the callback in my WindowProc(), like this:

...
case WM_NOTIFY:
{
  NMHDR *nm = (NMHDR *) lParam;
  switch (nm->code)
  {
    case TTN_GETDISPINFOW:
    {
      static std::wstring tip_string = L"Some random unicode string.";
      NMTTDISPINFOW *nmtdi = (NMTTDISPINFOW *) lParam;              
      nmtdi->lpszText = (LPWSTR) tip_string.c_str();
    }
    break;
  }
}
break;
...

Which does not work, as I never receive the TTN_GETDISPINOW message. (Note: It works if I handle TTN_GETDISPINFO instead and use NMTTDISPINFO to provide a char array, but then no unicode support...)

I'm guessing I'm doing something wrong in my setup or message handling here? Any suggestions on how to do it properly?

Update
Also note that my project is not compiled in unicoe mode (i.e. _UNICODE is not defined and the project is set to use multi-byte character set). This is intentional and I would like to keep it like that as, I have no desire to rewrite the entire application to be unicode-aware (at least not yet). Since the _UNICODE define is used to select *W versions of various functions and data structures I was hoping I could achieve the same result by using these explicitly in my code, as shown above.


Solution

  • Thanks for the Robert Scott link. I found a way to solve it now.

    In short, the trick was to make sure the receiving window was a unicode window and register a unicode window procedure for it.

    The problem was that I did not have a unicode WindowProc() for my parent window handling the TTN_GETDISPINFOW notification message. Since this window (class) was created with RegisterClassEx()/CreateWindowEx() and not RegisterClassExW()/CreateWindowExW(), it did not have registered window procedure for unicode messages.

    To get around the problem I changed ti.hwnd from hwnd_main to hwnd_control when sending TTM_ADDTOOLW, resulting in the control's window procedure receving the notifications instead of its parent. In order to intercept the unicode events now sent to the control's window procedure, I subclassed it using SetWindowLongW(hwnd_control, GWL_WNDPROC, (LONG) NewControlWndProc).

    Note that hwnd_control is a standard "LISTBOX" window created with CreateWindowExW() and is therefore unicode-aware, since all buildt-in Windows classes are automatically registered in both unicode and ANSI version by the system.