Еще один способ придать вашим программам профессиональный вид - использование всплывающих подсказок (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 и главному окну.