在编辑控件中显示无效输入的工具提示 [英] Show tooltip on invalid input in edit control

查看:149
本文介绍了在编辑控件中显示无效输入的工具提示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有子类编辑控件只接受浮点数。当用户输入无效时,我想弹出工具提示。我定位的行为就像一个 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 using EM_SHOWBALLOONTIP and EM_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆