I have
subclassed
edit control to accept only
floating numbers
. I would like to pop a
tooltip when user makes an invalid input. The behavior I target is like the one edit control with
ES_NUMBER
has, please see
this[
^].
So far I was able to implement
tracking tooltip
and display it when user makes invalid input.
However, the
tooltip is misplaced. I have tried to use
ScreenToClient
and
ClientToScreen
to fix this but have failed.
Here are the instructions for creating
SCCE :
1) Create default Win32 project in Visual Studio.
2) Add the following includes in your
stdafx.h
, just under
#include <windows.h>
:
#include <windowsx.h>
#include <commctrl.h>
#pragma comment( lib, "comctl32.lib")
#pragma comment(linker, \
"\"/manifestdependency:type='Win32' "\
"name='Microsoft.Windows.Common-Controls' "\
"version='6.0.0.0' "\
"processorArchitecture='*' "\
"publicKeyToken='6595b64144ccf1df' "\
"language='*'\"")
3) Add these global variables:
HWND g_hwndTT;<br />
TOOLINFO g_ti;
4) Here is a simple subclass procedure for edit controls ( just for testing purposes ) :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
POINT pt;
if( ! isdigit( wParam ) ) {
if (GetCaretPos(&pt)) {
ClientToScreen( hwnd, &pt );
ScreenToClient( GetParent(hwnd), &pt );
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
TRUE, (LPARAM)&g_ti);
SendMessage(g_hwndTT, TTM_TRACKPOSITION,
0, MAKELPARAM(pt.x, pt.y));
}
return FALSE;
}
else
{
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&g_ti);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
5) Add the following
WM_CREATE
handler :
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, L"EDIT", L"edit", WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );
g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
if( !g_hwndTT )
MessageBeep(0);
g_ti.cbSize = sizeof(TOOLINFO);
g_ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
g_ti.hwnd = hWnd;
g_ti.hinst = hInst;
g_ti.lpszText = TEXT("Hi there");
if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
MessageBeep(0);
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
6) Initialize common controls in
MyRegisterClass
( before
return
statement ) :
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES |
ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_STANDARD_CLASSES ;
if( !InitCommonControlsEx(&iccex) )
MessageBeep(0);
That's it, for the
SSCCE.
My questions are following :
1. How can I properly position
tooltip in my main window? How should I manipulate with caret coordinates?
2. Is there a way for
tooltip handle
and
toolinfo structure
to
not be global?
Thank you for your time.
Best regards.
EDIT #1:
I have managed to achieve quite an improvement by deleting
ScreenToClient
call in the subclass procedure. The x-coordinate is good, y-coordinate could be slightly lower. I still would like to remove global variables somehow...
EDIT #2:
I was able to adjust y-coordinate by using
EM_GETRECT[
^] message and setting y-coordinate to the bottom of the formatting rectangle:
RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClient.bottom;
Now the end-result is much better. All that is left is to remove global variables...
EDIT #3:
It seems that I have cracked it! The solution is in
EM_SHOWBALLOONTIP
and
EM_HIDEBALLOONTIP
messages! Tooltip is placed at the caret position, ballon shape is the same as the one on the picture, and it auto-dismisses itself properly. And the best thing is that I do not need global variables!
Here is my subclass procedure snippet:
case WM_CHAR:
{
if( ! IsCharAlpha( wParam ) && IsCharAlphaNumeric( wParam ) )
{
SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
else
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof( EDITBALLOONTIP );
ebt.pszText = L" Tooltip text! ";
ebt.pszTitle = L" Tooltip title!!! ";
ebt.ttiIcon = TTI_ERROR_LARGE;
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
}
break;