在编辑控件中显示无效输入的工具提示 [英] Show tooltip on invalid input in edit control
问题描述
我有子类
编辑控件只接受浮点数
。当用户输入无效时,我想弹出工具提示。我定位的行为就像一个 ES_NUMBER
的编辑控件,请参阅这个 [ ^ ]。
到目前为止,我能够实现跟踪工具提示
并在用户输入无效信息时显示。
但是, 工具提示放错了地方。我曾尝试使用 ScreenToClient
和 ClientToScreen
来修复此问题,但失败了。
以下是创建 SCCE 的说明:
1)在Visual Studio中创建默认的Win32项目。 />
2)在 stdafx.h
中添加以下包含,只需 #include< ; windows.h>
:
#include < windowsx.h >
#include < commctrl.h >
#pragma comment(lib,comctl32.lib)
#pragma comm ent(链接器,\
\/ manifestdependency:type ='Win32' \
name ='Microsoft.Windows.Common-Controls' \
version ='6.0.0.0' \
processorArchitecture ='*' \
publicKeyToken = '6595b64144ccf1df' \
language ='*'\)
3)添加全局变量:
HWND g_hwndTT;< br />
TOOLINFO g_ti;
4)这是编辑控件的简单子类程序(仅用于测试目的):
LRESU LT CALLBACK EditSubProc(HWND hwnd,UINT消息,
WPARAM wParam,LPARAM lParam,
UINT_PTR uIdSubclass,DWORD_PTR dwRefData)
{
switch (消息)
{
case WM_CHAR:
{
POINT pt;
if (!isdigit(wParam)) // 如果不是数字弹出工具提示!
{
if (GetCaretPos(& pt)) // 问题出现了
{
/ / 坐标不好,因此工具提示错位
ClientToScreen(hwnd,& pt);
/ * **************** ********编辑:**************************** /
/ * *******删除此行后x坐标为OK ******** /
/ * ** y坐标可能会低一些,但仍然可以** * /
/ * ************** ********************************************** / 跨度>
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)添加以下 WM_CREATE
处理程序:< br $>
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 ,< span class =code-digit> 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( 你好);
if (!SendMessage(g_hwndTT,TTM_ADDTOOL, 0 ,(LPARAM) & g_ti))
MessageBeep( 0 ); // 只是为了得到一些错误信号
// 子类编辑控件
SetWindowSubclass(hEdit,EditSubProc, 0 , 0 );
}
return 0L;
6)初始化 MyRegisterClass
中的常用控件(在 return
声明):
// 初始化常用控件
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 < /跨度>); // 信号错误
就是这样,对于 SSCCE 。
我的问题如下:
1.如何在主窗口中正确定位工具提示?我应该如何使用插入符坐标进行操作?
2.有没有办法工具提示句柄
和 toolinfo结构
到不是是全球性的吗?
感谢您的时间。
祝你好运。
编辑#1:
通过在子类程序中删除 ScreenToClient
调用,我成功地取得了相当大的进步。 x坐标很好,y坐标可能略低。我仍然想以某种方式删除全局变量...
编辑#2:
我可以使用 EM_GETRECT [ ^ ]消息并将y坐标设置到格式化矩形的底部:
RECT rcClientRect;
Edit_GetRect(hwnd,& rcClientRect);
pt.y = rcClient.bottom;
现在最终结果要好得多。剩下的就是删除全局变量...
编辑#3:
似乎我破解了它!解决方案是 EM_SHOWBALLOONTIP
和 EM_HIDEBALLOONTIP
消息!工具提示位于插入符号位置,气球形状与图片上的气球形状相同,并且它会自动自动解散。最好的是我不需要全局变量!
这是我的子类程序片段:
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 工具提示文字!;
ebt.pszTitle = L 工具提示标题!!!;
ebt.ttiIcon = TTI_ERROR_LARGE; // 工具提示图标
SendMessage(hwnd,EM_SHOWBALLOONTIP, 0 ,(LPARAM)& ebt);
return FALSE;
}
}
break ;
经过进一步测试后,我决定将其作为答案,以便其他人可以清楚地发现它。
解决方案是使用EM_SHOWBALLOONTIP
和EM_HIDEBALLOONTIP
消息。您无需创建工具提示并将其与编辑控件相关联!因此,我现在需要做的只是子类编辑控件,一切正常:
LRESULT CALLBACK EditSubProc(HWND hwnd,UINT消息) ,
WPARAM wParam,LPARAM lParam,
UINT_PTR uIdSubclass,DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CHAR:
{
if (! isdigit(wParam)) // 如果不是数字弹出工具提示!
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof (EDITBALLOONTIP);
ebt.pszText = L 工具提示文字!;
ebt.pszTitle = L 工具提示标题!!!;
ebt.ttiIcon = TTI_ERROR_LARGE; // 工具提示图标
SendMessage(hwnd,EM_SHOWBALLOONTIP, 0 ,(LPARAM)& ebt);
return FALSE;
}
else
{
SendMessage(hwnd,EM_HIDEBALLOONTIP, 0 , 0 );
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);
}
就是这样!
希望这个答案对某人也有帮助!
顺便提一下,数字编辑工具提示中有一个更有趣的部分,因为它里面有十字图标,我正在试图弄清楚他们是如何做到这一点的。我也没有点击工具提示解散它,所以还有更多的东西需要解决。
你已经过度复杂了它完成所有事情本身你根本不需要坐标而你需要使用CreateWindowEx,因为你想要WS_EX_TOPMOST,这里尝试这个例程。如果使用unicode工具提示,你已经获得了所需的manifest pragma并且安全添加#define _WIN32_WINNT 0x0600
/ * ---- -------------------------------------------------- --------------------
传入任何窗口句柄和工具提示字符串,此函数将设置
创建工具提示以显示在窗口,如果你将鼠标悬停在它上面。
---------------------------------------------- --------------------------- * /
HWND AddToolTip(HWND hWnd,//将工具提示放在$ b $以上的窗口句柄b TCHAR * tooltip){//文本工具提示应该说
TOOLINFO ti;
HWND TTWnd;
if(tooltip == 0)return(0); //检查我们有一个工具提示
InitCommonControls(); //检查公共控件是否已初始化
TTWnd = CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,
NULL,WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,hWnd ,0,0,0); //创建工具提示窗口
memset(& ti,0,sizeof(TOOLINFO)); //清除结构
ti.cbize = sizeof(TOOLINFO); //结构大小
ti.uFlags = TTF_SUBCLASS; //类是子类
ti.hwnd = hWnd; //父窗口
ti.hinst = 0; //这个例子
ti.uId = 0; //没有uid
ti.lpszText = tooltip; //传输文本指针
GetClientRect(hWnd,& ti.rect); //覆盖整个窗口的工具提示
SendMessage(TTWnd,TTM_ADDTOOL,0,(LPARAM)& ti); //添加工具提示
return(TTWnd); //返回工具提示窗口
};
您可以使用TTM_ACTIVATE消息控制工具提示是否显示开始激活,因此您可能需要在创建它之后关闭它,因为您希望如何使用它只出现错误。
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 not a number pop a tooltip!
{
if (GetCaretPos(&pt)) // here comes the problem
{
// coordinates are not good, so tooltip is misplaced
ClientToScreen( hwnd, &pt );
/************************* EDIT : ****************************/
/******** After I delete this line x-coordinate is OK ********/
/*** y-coordinate could be a little lower, but is still OK ***/
/*************************************************************/
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 );
// try with tooltip
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); // just to signal error somehow
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); // just to have some error signal
// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
6) Initialize common controls in MyRegisterClass
( before return
statement ) :
// initialize common controls
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); // signal error
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:
{
// whatever... This condition is for testing purpose only
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; // tooltip icon
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
}
break;
After further testing, I have decided to put this as an answer so others can clearly spot it.
The solution is in usingEM_SHOWBALLOONTIP
andEM_HIDEBALLOONTIP
messages. You do not need to create tooltip and associate it to an edit control! Therefore, all I need to do now is simply subclass edit control and everything works :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) { switch (message) { case WM_CHAR: { if( ! isdigit( wParam ) ) // if not a number pop a tooltip! { EDITBALLOONTIP ebt; ebt.cbStruct = sizeof( EDITBALLOONTIP ); ebt.pszText = L" Tooltip text! "; ebt.pszTitle = L" Tooltip title!!! "; ebt.ttiIcon = TTI_ERROR_LARGE; // tooltip icon SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt); return FALSE; } else { SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0); 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); }
That's it!
Hopefully this answer will help someone too!
By the way there was a more interesting part of the number edit tooltip in that it has the cross icon in it and I am currently playing around trying to work out how they did that. I also not clicking on the tooltip dismisses it so got a couple more things to work out.
You are over complicating it does everything itself you don't need co-ordinates at all and you need to use CreateWindowEx because you want WS_EX_TOPMOST, here try this routine. You have already got the manifest pragma you need if using unicode tooltips and to be safe add #define _WIN32_WINNT 0x0600
/*-------------------------------------------------------------------------- Pass in any window handle and a tooltip string and this function will set the create a tooltip to display on the window if you hover over it. -------------------------------------------------------------------------*/ HWND AddToolTip (HWND hWnd, // Window handle to put tooltip over TCHAR* tooltip) { // Text the tool tip should say TOOLINFO ti; HWND TTWnd; if (tooltip == 0) return (0); // Check we have a tooltip InitCommonControls(); // Check common controls are initialized TTWnd = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, 0, 0, 0); // Create tooltip window memset(&ti, 0, sizeof(TOOLINFO)); // Clear structure ti.cbSize = sizeof(TOOLINFO); // Size of structure ti.uFlags = TTF_SUBCLASS; // Class is subclass ti.hwnd = hWnd; // Parent window ti.hinst = 0; // This instance ti.uId = 0; // No uid ti.lpszText = tooltip; // Transfer the text pointer GetClientRect (hWnd, &ti.rect); // Tooltip to cover whole window SendMessage(TTWnd, TTM_ADDTOOL, 0, (LPARAM) &ti);// Add tooltip return(TTWnd); // Return the tooltip window };
You can control if the tooltip shows or not using the TTM_ACTIVATE message and it starts active so you probably need to turn it off after you create it because of how you want to use it to only appear on error.
这篇关于在编辑控件中显示无效输入的工具提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!