在编辑控件中显示无效输入的工具提示

Alw*_*uff 11 c++ winapi tooltip editcontrol

我有子类编辑控件只接受浮动数字.我想在用户输入无效时弹出工具提示.我的目标行为就像一个带有的编辑控件ES_NUMBER:

在此输入图像描述

到目前为止,我能够实现跟踪工具提示并在用户进行无效输入时显示它.

但是,工具提示是错误的.我试图使用ScreenToClientClientToScreen修复此问题但失败了.

以下是创建SCCE的说明:

1)在Visual Studio中创建默认的Win32项目.

2)在您的stdafx.h下方添加以下内容#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='*'\"")
Run Code Online (Sandbox Code Playgroud)

3)添加这些全局变量:

HWND g_hwndTT;
TOOLINFO g_ti;
Run Code Online (Sandbox Code Playgroud)

4)这是编辑控件的简单子类过程(仅用于测试目的):

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 #1 ****************************/
                    /******* If I delete this line x-coordinate is OK *************/
                    /*** y-coordinate should be little lower, but it is still OK **/
                    /**************************************************************/

                    ScreenToClient( GetParent(hwnd), &pt );

                    /************************* Edit #2 ****************************/

                    // this adjusts the y-coordinate, see the second edit
                    RECT rcClientRect;
                    Edit_GetRect( hwnd, &rcClientRect );
                    pt.y = rcClientRect.bottom;

                    /**************************************************************/

                    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);
} 
Run Code Online (Sandbox Code Playgroud)

5)添加以下WM_CREATE处理程序:

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;  
Run Code Online (Sandbox Code Playgroud)

6)初始化MyRegisterClass(return声明前)的常用控件:

// 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 
Run Code Online (Sandbox Code Playgroud)

对于SSCCE来说就是这样.

我的问题如下:

  1. 如何在主窗口中正确定位工具提示?我应该如何操纵插入符号坐标?

  2. 工具提示句柄和t​​oolinfo结构是否有办法不是全局的?

感谢您的时间.

最好的祝福.

编辑#1:

通过删除ScreenToClient子类过程中的调用,我设法实现了相当大的改进.x坐标很好,y坐标可能略低.我还是想以某种方式删除全局变量......

编辑#2:

我可以通过使用EM_GETRECT消息并将y坐标设置到格式化矩形的底部来调整y坐标:

RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClient.bottom;
Run Code Online (Sandbox Code Playgroud)

现在最终结果要好得多.剩下的就是删除全局变量......

编辑#3:

好像我已经破解了!解决方案是EM_SHOWBALLOONTIPEM_HIDEBALLOONTIP消息!工具提示位于插入符号位置,气球形状与图片上的气球形状相同,并且它会自动自动解散.最好的是我不需要全局变量!

这是我的子类过程片段:

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;
Run Code Online (Sandbox Code Playgroud)

Alw*_*uff 5

经过进一步测试,我决定将其作为答案,以便其他人可以清楚地发现它。

解决方案是使用EM_SHOWBALLOONTIPEM_HIDEBALLOONTIP消息。您无需创建工具提示并将其关联到编辑控件!因此,我现在要做的只是子类编辑控件,一切正常:

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);
} 
Run Code Online (Sandbox Code Playgroud)

而已!

希望这个答案也能对某人有所帮助!


Edw*_*nts 4

我将评论作为答案(我应该早点这样做),以便清楚地表明问题已得到解答:

TTM_TRACKPOSITION 的 MSDN 文档表示 x/y 值是“在屏幕坐标中”。

我不完全确定,但 y 坐标可能对应于插入符的顶部,如果您想将工具提示放置在编辑框的中间,您可以添加编辑框高度的一半。

编辑 全局变量,您可以将所有全局变量捆绑到一个结构中,为该结构分配内存,并使用编辑窗口的SetWindowLongPtr API 调用传递该结构的指针,然后窗口过程可以使用GetWindowLongPtrGWLP_USERDATA检索值...