Показывать всплывающую подсказку о неверном вводе в редакторе

У меня есть подкласс управления правами, чтобы принимать только плавающие числа. Я хотел бы получить всплывающую подсказку, когда пользователь делает неверный ввод. Поведение, на которое я нацеливаюсь, похоже на один элемент управления редактирования ES_NUMBER имеет:

введите описание изображения здесь

До сих пор мне удавалось реализовать всплывающую подсказку для отслеживания и отображать ее, когда пользователь вводит неверные данные.

Тем не менее, всплывающая подсказка неуместна. Я пытался использовать ScreenToClient а также ClientToScreen исправить это, но не удалось.

Вот инструкция по созданию ГККП :

1) Создайте проект Win32 по умолчанию в Visual Studio.

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='*'\"")

3) Добавьте эти глобальные переменные:

HWND g_hwndTT;
TOOLINFO g_ti;

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);
}

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;

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

Вот и все, для SSCCE.

Мои вопросы следующие:

  1. Как правильно расположить подсказку в главном окне? Как мне следует манипулировать координатами каретки?

  2. Есть ли способ, чтобы дескриптор подсказки и структура toolinfo не были глобальными?

Спасибо за ваше время.

С наилучшими пожеланиями.

РЕДАКТИРОВАНИЕ № 1:

Мне удалось добиться значительного улучшения, удалив ScreenToClient вызов в процедуре подкласса. Х-координата хорошая, у-координата может быть немного ниже. Я все еще хотел бы удалить глобальные переменные как-то …

РЕДАКТИРОВАТЬ № 2:

Я был в состоянии настроить координату Y с помощью EM_GETRECT сообщение и установка y-координаты внизу прямоугольника форматирования:

RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClient.bottom;

Теперь конечный результат намного лучше. Осталось только удалить глобальные переменные …

РЕДАКТИРОВАТЬ № 3:

Кажется, я это взломал! Решение в EM_SHOWBALLOONTIP а также EM_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;

9

Решение

Я даю комментарий в качестве ответа (я должен был сделать это раньше), чтобы было ясно, что на вопрос дан ответ:

Документы MSDN для TTM_TRACKPOSITION говорит, что значения х / у «в экранных координатах».

Я не совсем уверен, но координата y, вероятно, соответствует вершине каретки, вы можете добавить половину высоты поля редактирования, если хотите расположить подсказку в середине поля редактирования.

РЕДАКТИРОВАТЬ
Что касается глобальных переменных, вы можете объединить все свои глобальные переменные в структуру, выделить память для структуры и передать указатель структуры, используя SetWindowLongPtr Вызов API для окна редактирования с использованием GWLP_USERDATAокно proc может затем получить значения, используя GetWindowLongPtr

4

Другие решения

После дальнейшего тестирования я решил поставить это в качестве ответа, чтобы другие могли четко это определить.

Решение в использовании EM_SHOWBALLOONTIP а также EM_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);
}

Это оно!

Надеюсь, этот ответ тоже кому-нибудь поможет!

4

В качестве продолжения комментариев, касающихся использования функции SetProp для устранения необходимости удерживать пару глобалов для данных всплывающей подсказки, я представляю следующее решение.

Примечание: путем проверки ошибок при вызовах GetPropЯ разработал WndProc для элемента управления редактирования подкласса, который будет работать независимо от того, было ли желательно использовать подсказки. Если свойство не найдено, я просто опускаю любой код обработки подсказок.

Примечание 2: Одним из недостатков всех доступных подходов к тому, чтобы сделать информацию всплывающей подсказки неглобальной, является то, что она вводит связь между подклассом WndProc и wndProc родительского окна.

  • Используя dwRefDataнеобходимо проверить, что он содержит ненулевое значение
    указатель.
  • Используя SetWindowLongPtrнужно помнить индекс в
    данные пользователя.
  • Используя SetPropнужно помнить текстовое имя свойства. я нахожу
    это проще.

Удаление вызова SetProp удаляет функциональность подсказки. Т.е. вы можете использовать один и тот же подкласс wndProc для элементов управления редактированием, независимо от того, используют ли они всплывающие подсказки или нет.

Anyhoo, дальше с кодом (Code :: Blocks).

#define _WIN32_IE 0x0500
#define _WIN32_WINNT 0x0501

#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif

#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <ctype.h>
#include <cstdio>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");HWND g_hwndTT;
TOOLINFO g_ti;
typedef struct mToolTipInfo
{
HWND hwnd;
TOOLINFO tInfo;
} * p_mToolTipInfo;LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));

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 );

RECT lastCharRect;
lastCharRect.left = lastCharRect.top = 0;
lastCharRect.right = lastCharRect.bottom = 32;

HDC editHdc;
char lastChar;
int charHeight, charWidth;

lastChar = (char)wParam;
editHdc = GetDC(hwnd);
charHeight = DrawText(editHdc, &lastChar, 1, &lastCharRect, DT_CALCRECT);
charWidth = lastCharRect.right;
ReleaseDC(hwnd, editHdc);

//pt.x += xOfs + charWidth; // invalid char isn't drawn, so no need to advance xPos to reflect width of last char
pt.y += charHeight;

if (tmp)
{
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&tmp->tInfo);
SendMessage(tmp->hwnd, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y));
}
}
return FALSE;
}
else
{
if (tmp)
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&tmp->tInfo  );
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;

case WM_DESTROY:
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));
if (tmp)
{
delete(tmp);
RemoveProp(hwnd, _T("tipData"));
}
}
return 0;

case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}

HINSTANCE hInst;

int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd;               /* This is the handle for our window */
MSG messages;            /* Here messages to the application are saved */
WNDCLASSEX wincl;        /* Data structure for the windowclass */

/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);

/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL;                 /* No menu */
wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
wincl.cbWndExtra = 0;                      /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;

/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0,                   /* Extended possibilites for variation */
szClassName,         /* Classname */
_T("Code::Blocks Template Windows App"),       /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT,       /* Windows decides the position */
CW_USEDEFAULT,       /* where the window ends up on the screen */
544,                 /* The programs width */
375,                 /* and height in pixels */
HWND_DESKTOP,        /* The window is a child-window to desktop */
NULL,                /* No menu */
hThisInstance,       /* Program Instance handler */
NULL                 /* No Window Creation data */
);

/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);

/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}

/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)                  /* handle the messages */
{
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, _T("EDIT"), _T("edit"), WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );

p_mToolTipInfo tmp = new mToolTipInfo;
SetProp(hEdit, _T("tipData"), tmp);

// try with tooltip
//g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
tmp->hwnd = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);

//if( !g_hwndTT )
if( !tmp->hwnd )
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 = _T("Hi there");
tmp->tInfo.cbSize = sizeof(TOOLINFO);
tmp->tInfo.uFlags = TTF_TRACK | TTF_ABSOLUTE;
tmp->tInfo.hwnd = hWnd;
tmp->tInfo.hinst = hInst;
tmp->tInfo.lpszText = _T("Hi there");

//            if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
if( ! SendMessage(tmp->hwnd, TTM_ADDTOOL, 0, (LPARAM)&tmp->tInfo) )
MessageBeep(0);  // just to have some error signal

// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;

case WM_DESTROY:
PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
break;
default:                      /* for messages that we don't deal with */
return DefWindowProc (hWnd, message, wParam, lParam);
}

return 0;
}
2
По вопросам рекламы [email protected]