Подсказки (tooltips) в диалоговых окнах

Еще один способ придать вашим программам профессиональный вид - использование всплывающих подсказок (tooltips), которые появляются при наведении курсора мыши на какой-нибудь элемент диалогового окна. Готовые примеры на FASM или слишком громоздкие, или недостаточно гибкие, поэтому пришлось придумывать что-то свое. Причем с самого начала поставил себе задачу сделать функции работы с подсказками как можно более универсальными, чтобы в дальнейшем их можно было легко использовать в других проектах.

В сегменте данных определим все необходимые константы, переменные и структуры. Большая часть этих данных в FASM по умолчанию отсутствует, так что пришлось штудировать MSDN и другие источники и описывать их самостоятельно.
; Сегмент данных
section '.data' data readable writeable  
 
hwndTip         dd ?    ; Хэндл окна подсказки
TTipFlag        dd ?    ; Флаг активности подсказки
 
; Структура TRACKMOUSEEVENT не определена, сделаем это сами
struct TRACKMOUSEEVENT
  cbSize        dd ?
  dwFlags       dd ?
  hwndTrack     dd ?
  dwHoverTime   dd ?
ends
 
; Определяем нужные структуры
tme     TRACKMOUSEEVENT
pt      POINT
ttip    TOOLINFO
trect   RECT
 
oldX    dd ?    ; Сохраненные координаты мыши
oldY    dd ?
 
; Определяем нужные константы
TTM_TRACKACTIVATE  = WM_USER + 17
TTM_TRACKPOSITION  = WM_USER + 18
TTM_SETMAXTIPWIDTH = WM_USER + 24
 
; Сообщения окну подсказки
TTF_SUBCLASS       = 0x0010
TTF_TRACK          = 0x0020
TTF_ABSOLUTE       = 0x0080
TTF_TRANSPARENT    = 0x0100
 
; Сообщение обработчика мыши
TME_LEAVE          = 0x00000002
 
; Максимальная ширина всплывающей подсказки
TOOLTIP_WIDTH      = 200
 
; Предустановленное количество элементов в массиве подсказок
TOOLTIPS_COUNT     = 5
 
; Массив данных для всплывающих подсказок
; Формат массива:
;   1 DWORD - сохраненный адрес обработчика
;   2 DWORD - адрес подсказки
tt_data rd  (TOOLTIPS_COUNT*2)

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

Параметры вызова: hwnd - хэндл окна, к которому добавляется подсказка, lpText - указатель на строку подсказки в формате ASCIIZ, lpData - указатель на два DWORD (элемент массива), в которых будет храниться адрес предыдущей процедуры обработчика окна и указатель на строку подсказки.
;----------------------------------------------------------------------
; Процедура добавления подсказки к окну
; Входные параметры:
;   hwnd   - хэндл окна, к которому добавляется подсказка
;   lpText - указатель на строку подсказки
;   lpData - указатель на данные (2 DWORD)
;----------------------------------------------------------------------
proc AddTooltip hwnd:DWORD, lpText:DWORD, lpData:DWORD
        pusha
 
        ; Оно подсказки уже инициализировано?
        cmp     [hwndTip],0
        jne     @f
 
        invoke  GetModuleHandle,0
        ; Создать окно подсказки
        invoke  CreateWindowEx, NULL, ttclass, NULL, TTS_ALWAYSTIP,\
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,\
                NULL, NULL, eax, NULL
        ; Сохранить хэндл окна подсказки
        mov     [hwndTip],eax
 
        ; Определить константу CS_DROPSHADOW
        CS_DROPSHADOW = 00020000h
        ; Получить текущее значение стиля окна
        invoke  GetWindowLong,[hwndTip],GCL_STYLE
        ; Добавить к нему атрибут тень
        or      eax,CS_DROPSHADOW
        ; Установить новый стиль окна
        invoke  SetClassLong,[hwndTip],GCL_STYLE,eax
 
        ; Заполнение структуры для подсказки
        mov     [ttip.cbSize],sizeof.TOOLINFO
        mov     [ttip.hwnd],NULL
        mov     [ttip.uFlags],TTF_IDISHWND+TTF_TRACK+TTF_ABSOLUTE+TTF_SUBCLASS
        mov     [ttip.uId],NULL
        mov     [ttip.lpszText],szNull
 
        ; Создать подсказку
        invoke  SendMessage,[hwndTip],TTM_ADDTOOL,0,ttip
        ; Установить максимальную ширину. При этом длинный текст будет
        ; переноситься на следующую строку
        invoke  SendMessage,[hwndTip],TTM_SETMAXTIPWIDTH,0,TOOLTIP_WIDTH
        ; Установить подсказку поверх всех окон
        invoke  SetWindowPos,[hwndTip], HWND_TOPMOST,0,0,0,0,\
                SWP_NOMOVE+SWP_NOSIZE+SWP_NOACTIVATE
@@:
        ; Субклассировать окно для вывода подсказки
        invoke  SetWindowLong,[hwnd],GWL_WNDPROC,ToolTipProc
        mov     edi,[lpData]
        stosd
        mov     eax,[lpText]
        stosd
        ; Сохранить в пользовательских данных окна указатель на массив
        invoke  SetWindowLong,[hwnd],GWL_USERDATA,[lpData]
 
        popa
        ret
 
ttclass db 'tooltips_class32',0
szNull  db ' ',0
 
endp

Адрес элемента массива привязывается к самому окну, сохраняясь функцией SetWindowLong в пользовательские данные. Процедура универсальная и позволяет добавлять подсказки к любым окнам. При этом надо следить, чтобы пользовательские данные окна GWL_USERDATA после установки обработчика больше не изменялись. Если подсказка добавлятся к элементу типа STATIC, то в ресурсах он должен быть описан с флагом SS_NOTIFY.

Обычная подсказка появляется при наведении курсора на элемент и остается видимой заданное время. Но гораздо лучше выглядит подсказка, которая перемещается за курсором. Субклассированная процедура обработки окна как раз отслеживает перемещение курсора мыши и перемещает окно подсказки следом за ним. Адрес текста подсказки и адрес предыдущего обработчика берется из пользовательских данных окна.
;----------------------------------------------------------------------
; Субклассированная процедура обработки всплывающей подсказки
;----------------------------------------------------------------------
proc ToolTipProc hwnddlg:DWORD,msg:DWORD,wparam:DWORD,lparam:DWORD
        cmp     [msg],WM_MOUSELEAVE
        je      .wmleave
        cmp     [msg],WM_MOUSEMOVE
        je      .wmmouse
 
        ; Получить адрес предыдущего обработчика
        invoke  GetWindowLong,[hwnddlg],GWL_USERDATA
        mov     eax,[eax]
        ; Передать управление предыдущему обработчику
        invoke  CallWindowProc,eax,[hwnddlg],[msg],[wparam],[lparam]
        ret
 
.wmleave:
        ; Скрыть подсказку
        invoke  SendMessage,[hwndTip], TTM_TRACKACTIVATE, FALSE, ttip
        mov     [TTipFlag],FALSE
        jmp     .finish
 
.wmmouse:
        ; Подсказка уже показывается?
        cmp     [TTipFlag],TRUE
        je      @f
 
        ; Установить перехватчик мыши на событие
        mov     [tme.cbSize],sizeof.TRACKMOUSEEVENT
        mov     eax,[hwnddlg]
        mov     [tme.hwndTrack],eax
        mov     [tme.dwFlags],TME_LEAVE
        invoke  TrackMouseEvent,tme
 
        ; Получить адрес строки подсказки
        invoke  GetWindowLong,[hwnddlg],GWL_USERDATA
        mov     eax,[eax+4]
 
        ; Установить новый текст подсказки
        mov     [ttip.lpszText],eax
        ; Обновить подсказку
        invoke  SendMessage,[hwndTip], TTM_SETTOOLINFO, TRUE, ttip
        ; Включить отображение подсказки
        invoke  SendMessage,[hwndTip], TTM_TRACKACTIVATE, TRUE, ttip
        ; Установить флаг, что подсказка уже отображается
        mov     [TTipFlag],TRUE
@@:
        ; Получить координаты мыши
        mov     esi,[lparam]     ; X
        and     esi,0FFFFh
        mov     edi,[lparam]     ; Y
        shr     edi,16
 
        ; Фактическое движение мыши было?
        cmp     esi,[oldX]
        jne     @f
        cmp     edi,[oldY]
        je      .finish
@@:
        ; Сохранить новые координаты
        mov     [pt.x],esi
        mov     [oldX],esi
        mov     [pt.y],edi
        mov     [oldY],edi
 
        ; Вычислить абсолютные координаты на экране
        invoke  ClientToScreen,[hwnddlg],pt
        mov     ebx,[pt.y]
 
        ; Получить высоту окна подсказки
        invoke  GetWindowRect,[hwndTip],trect
        mov     eax,[trect.top]
        sub     eax,[trect.bottom]
        add     ebx,eax
 
        shl     ebx,16
        add     ebx,[pt.x]
        add     ebx,5
 
        ; Установить подсказку на новые координаты
        invoke  SendMessage,[hwndTip], TTM_TRACKPOSITION, 0, ebx
.finish:
        xor     eax,eax
        ret
endp

Подключение подсказки выполняется следующим образом. В примере подразумевается, что структура tt_data инициализирована, как показано в начале статьи.
        ...
        ; Получить хэндл элемента окна
        invoke  GetDlgItem,[hwnddlg],ID_EDIT1
        stdcall AddTooltip,eax,szTip1,tt_data+(0*8)
 
        ; Получить хэндл элемента окна
        invoke  GetDlgItem,[hwnddlg],ID_EDIT2
        stdcall AddTooltip,eax,szTip2,tt_data+(1*8)
 
        ; Получить хэндл элемента окна
        invoke  GetDlgItem,[hwnddlg],IDCANCEL
        stdcall AddTooltip,eax,szTip3,tt_data+(2*8)
        ...

В приложении пример диалогового окна с подсказками, добавленными к полям EDIT, кнопке, элементу STATIC и главному окну.
Теги:
Подсказки, tooltips, диалоговые окна
Добавлено: 10 Апреля 2018 17:57:06 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...