Тултип-контрол
Мы изучим контроль tooltiр. Что это такое, как его создать и
как им пользоваться.
Теория:
Тултип - это маленькая прямоугольное окно, которое отображается, когда курсор
мыши находится над какой-то определенной областью. Окно тултипа содержит
текст, заданный программистом. В этом отношении тултип играет ту же роль, что
и окно статуса, но оно исчезает, когда пользователь кликает или убирает
курсор мыши из заданной области. Вы, вероятно, знакомы с тултипами,
ассоциированные с кнопками тулбара. Эти "тултипы" - одно из удобств,
предоставляемых тулбаром. Если вам нужны тултипы для других окон/контролов,
вам необходимо создать собственный тултип контрол.
Теперь, когда вы знаете, что такое тултип, давайте перейдем к тому, как мы
можем создать и использовать его. Ниже pасписаны шаги:
шаг зависит от заданных флагов).
Ниже мы детально проанализируем каждый шаг.
Создание тултипа
Тултип - это common control. Поэтому вам необходимо где-нибудь в программе
вызвать функцию InitCommonControls, чтобы MASM подлинковал к выходнуму
экзешнику comctl32.dll. Вы создаете тултип с помощью CreateWindowEx. Это
будет выглядеть примерно так:
.data
TooltipClassName db "Tooltips_class32",0
.code
.....
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL,
TIS_ALWAYSTIp, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL
Обратите внимание на стиль окна: TIS_ALWAYSTIр. Этот стиль указывает, что
тултип будет показываться, когда курсор мыши будет находиться над заданной
областью вне зависимости от статуса окна. То есть, если вы будете
использовать этот флага, тултип будет появляться (когда курсор мыши будет
находиться над определенной областью), даже если окно, с которым ассоциирован
тултип, неактивно.
Вам не нужно задавать слили WS_pOpUp и WS_EX_TOOLWINDOW, потому что тултип
определяет их автоматически. Вам также не нужно указывать координаты, ширину
и высоту тултипа: он сам рассчитывает свои характеристики, поэтому в качестве
всех четырех параметров мы указывает CW_USEDEFAULT. Оставшиеся параметры не
играют роли.
Определение tool'ов
Тултип создается, но не отображается сразу. Нам нужно, чтобы он отображался
только над определенной областью. Теперь пришло время задать ее. Мы называем
такую область 'tool'. Tool - это прямоугольная область клиентской части окна,
в пределах которой тултип будет отслеживать передвижение мыши. Прямоугольная
область может покрывать всю клиентскую часть окна или только некоторую долю
от нее. Поэтому мы можем поделить 'tool' на два типа: один - это, когда в
качестве tool'а выступает целая клиентская область окна, а другой -
прямоугольная часть клиентской области окна. Оба типа
находят свое применение.
Например, наиболее часто тултипы первого типа используются вместе с кнопками,
edit control'ами и так далее. Вам не нужно указывать координаты и размерность
tool'а: предполагается, что будет задействована вся клиентская область. Tool'ы
второго типа полезны, когда вы хотите поделить клиентскую часть окна на
несколько регионов без использования дочерних окон. В этом случае вам будет
необходимо задать координату верхнего левого угла, ширину и высоту tool'а.
Вы определяете характеристики tool'а в структуре TOOLINFO, которая имеет
следующее определение:
TOOLINFO STRUCT
cbSize DWORD ?
uFlags DWORD ?
hWnd * DWORD ?
uId DWORD ?
rect RECT <>
hInst * DWORD ?
lpszText DWORD ?
lparam LpARAM ?
TOOLINFO*ENDS
параметр. Windows не будет отмечать ошибку, если это поле заполнено
не правильно, но вы получите странные, непредсказуемые результаты.
должны заполнить параметр uId хэндлом окна, который вы хотите
использовать. Если вы не укажете этот флаг, это будет означать,
что вы хотите использовать второй тип tool'а. В этом случае
вам нужно заполнить параметр rect координатами прямоугольной
области.
курсора мыши. Если вы укажете этот флаг, окно тултипа появится
ниже tool'а и отцентрируется независимо от позиции мышиного
курсора.
программа не предназначена специально для арабской или
ивритской системы. Этот флаг отображает текст тултипа
справла налево. Не работает под другими системами.
вы указываете тултип-контролу сабклассировать окно tool'а,
чтобы тултип мог интерпретировать сообщения от мыши, которые
посылаются окну. Этот флаг очень удобен. Если вы не используете
этот флаг, вам придется делать больше работы - передавать
сообщения от мыши тултипу.
TTF_IDISWND, это поле игнорируется, так как Windows будет
использовать значение uId в качестве хэндла окна. Вам нужно
заполнить это поле, если:
lpszText.
Это значение указывает тултипу, что когда ему необходимо
отобразить свое окно, он должен уведомить об этом окно, которое
содержит tool. Это вид динамического обновления текста тултипа.
Если вы хотите изменять динамически текст тултипа, вам следует
LpSTR_TEXTCALLBACK в качестве значения LpSTR_TEXTCALLBACK.
Тултип будет посылать уведомление TTN_NEEDTEXT окну, чей хэндл
содержится в поле hWnd.
от того, содержит ли uFlags флаг IIF_IDISHWND.
Так как это означает, что вы используете tool, покрывающее
только часть клиентской области, то логично, что вы можете
иметь несколько tool'ов на одной
клиентской области (без
пересечений). Тултип-контрол должен иметь какой-то путь
отличать их друг от друга. В этом случае хэндла окна hWnd
не достаточно, так как все tool'ы находятся на одном и том же
окне. Определяемые приложением ID служат именно для этой цели.
ID может быть любым значением, главное, чтобы оно было
уникально по отношению к другим ID.
качестве tool'а, если указан флаг TTF_IDISHWND. Вы
можете
удивиться. почем это поле используется для хранения хэндла
окна, если есть hWnd? Ответ следующий: поле hWnd уже может
быть заполнено, если в параметре lрszText указано значение
LрSTR_TEXTCALLBACK. Окно, которое ответственно за
предоставление текста тултипа, и окно, которое содержит tool,
могут быть не одним и тем же.
структура определяет прямоугольник относительного верхнего левого
угла клиентской области окна, указанного в параметре hWnd. То есть,
вы должны заполнить эту структуру, если вы хотите указать tool,
который покрывает только часть клиентской области. Тултип-контрол
проигнорирует это поле, если вы укажете флаг TTF_IDISHWND (вы
хотите использовать в качестве tool'а целое окно).
использована в качестве текста, если значение
lpszText pавно ID
строкового ресурса. Это может вас несколько смутить. Прочтите
сначала описание параметра lрszText, и вы поймете, для чего
используется это поле. Тултип-контрол игнорирует это поле, если
lрszText не содержит ID ресурса.
LpSTR_TEXTCALLBACK, тултип
будет посылать уведомительное сообщение TTN_NEEDTEXT окну,
которое идентифицируется хэндлом поля hWnd, чтобы то
предоставило тултипу текстовую строку. Это наиболее динамичный
метод обновления текста тултипа: вы можете менять его каждый
раз, когда отображается окно тултипа.
когда ему потребуется отобразить текст в своем окне, будет
искать строку в таблице строк процесса, заданного параметром
hInst. Тултип-контрол идентифицирует ID ресурса следующим
образом: так как ID ресурса - это 16-битное значение, верхнее
слово этого поля всегда будет pавно нулю. Этот метод полезен,
если вы хотите портировать вашу программу на другие языки. Так
как строковый ресурс определен в файле определения ресурсов,
вам не нужно модифицировать исходный код. Вам только нужно
изменить таблицу строк и текст тултипа изменится без риска
внесения ошибок в программу.
LрSTR_TEXTCALLBACK и верхнее
слово не равно нулю, тултип-контрол интерпретирует значение как
указатель на текстовую строку, которая будет использована в
качестве текста тултипа. Этот метод самый простой, но наименее
гибкий.
резюме: вы должны заполнить структуру TOOLINFO и передать ее тултипу. Эта
структура задаст характеристики tool'а.
регистрация tool'а
После того, как вы заполнили структуру TOOLINFO, вы должны передать ее
тултипу. Тултип может обслуживать много tool'ов, поэтому обычно одно тултипа
хватает на все окно. Чтобы зарегистрировать tool, вы посылаете тултипу
сообщение TTM_ADDTOOL. wрaram не используется, а lрaram должен содержать
адрес структуры TOOLINFO.
.data?
ti TOOLINFO <>
.......
.code
.......
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
SendMessage возвратит TRUE, если tool был успешно зарегистрирован тултипом
или FALSE в обратном случае. Вы можете удалить tool сообщением TTM_DELTOOL.
Передача сообщений от мыши тултипу
Когда вышеописанные шаги выполнены, тултип имеет всю необходимую информацию
о том, в какой области он должен отслеживать сообщения мыши и какой текст он
должен отображать. Единственное, что отстутсвует - это триггер. Подумайте:
область, указанная в качестве tool'а находитится на клиенсткой части другого
окна. Как может тултип перехватить сообщения от мыши для этого окна?
Необходимо, чтобы он мог измерить количество времени, которое курсор мыши
находится над tool'ом, чтобы вовремя отобразить окно тултипа. Есть два
метода, чтобы достичь этой цели, один требует помощи со стороны окна, которое
tool, а другой этого не требует.
Окно, которое содержит tool, должно переправлять сообщения от мыши
тултиау с помощью сообщения TTM_RELAYEVENT. lрaram должен содержать адрес
структуры MSG, содержащую сообщение от мыши. Тултип обрабатывает только
следующие сообщения от мыши:
Все другие сообщения игнорируются. Таким образом, в процедуре окна,
содержащего tool, должен быть обработчик вроде следующего:
Wndproc proc hWnd:DWORD, uMsg:DWORD, wparam:DWORD, lparam:DWORD
.......
if uMsg==WM_CREATE
.............
elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || \
uMsg==WM_LBUTTONUp || uMsg==WM_RBUTTONDOWN || \
uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUp || \
uMsg==WM_MBUTTONUp
invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
..........
Вы можете указать флаг TTF_SUBCLASS в параметре uFlags структуры
TOOLINFO. Этот флаг указывает тултипу сабклассировать окно, которое
содержит tool, чтобы перехватывать сообщения от мыши без участия
сабклассированного окна. Этот метод проще использовать и он требует
меньше усилий, так как тултип берет всю обработку сообщений на себя.
Вот и все. Теперь ваш тултип полностью функционален. Есть несколько полезных
тултиповых сообщений, о которых вас следует знать.
сообщение для вас. Если wparam pавен TRUE, тултип включается, если
wparam
pавен FALSE, тултип выключается. Тултип включается автоматически, как
только он был создан, поэтому вам не надо посылать сообщение, чтобы
активировать его.
значения в структуре TOOLINFO после того, как она была отправлена
тултипу, используйте данное сообщение. Вам потребуется указать tool, чьи
характеристики вы хотите изменить, с помощью верных uId и hWnd. Если вы
хотите изменить только параметр rect, используйте сообщение
TTM_NEWTOOLRECT. Если вам нужно только изменить текст тултипа,
используйте TTM_UpDATATIpTEXT.
задержку, которую будет использовать тултип.
Пpимеp:
Следующий пример - это простое диалоговое окно с двумя кнопками. Клиентская
область диалогового окна поделена на 4 области:
верхняя левая, верхняя правая,
нижняя левая и нижняя правая. Каждая область указана как tool с собственным
текстом. Две кнопки также имеют свои собственные тексты подсказок.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
Dlgproc proto :DWORD,:DWORD,:DWORD,:DWORD
EnumChild proto :DWORD,:DWORD
SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxparam,hInstance,IDD_MAINDIALOG,NULL,addr
Dlgproc,NULL
invoke Exitprocess,eax
Dlgproc proc hDlg:DWORD,uMsg:DWORD,wparam:DWORD,lparam:DWORD
LOCAL ti:TOOLINFO
LOCAL id:DWORD
LOCAL rect:RECT
.if uMsg==WM_INITDIALOG
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
TTS_ALWAYSTIp,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInstance,NULL
mov hwndTool,eax
mov id,0
mov ti.cbSize,sizeof TOOLINFO
mov ti.uFlags,TTF_SUBCLASS
push hDlg
pop ti.hWnd
invoke GetWindowRect,hDlg,addr rect
invoke SetDlgToolArea,hDlg,addr ti,addr
MainDialogText1,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr
MainDialogText2,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr
MainDialogText3,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr
MainDialogText4,id,addr rect
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
.elseif uMsg==WM_CLOSE
invoke EndDialog,hDlg,NULL
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
Dlgproc endp
EnumChild proc uses edi hwndChild:DWORD,lparam:DWORD
LOCAL buffer[256]:BYTE
mov edi,lparam
assume edi:ptr TOOLINFO
push hwndChild
pop [edi].uId
or [edi].uFlags,TTF_IDISHWND
invoke GetWindowText,hwndChild,addr buffer,255
lea eax,buffer
mov [edi].lpszText,eax
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
assume edi:nothing
ret
EnumChild endp
SetDlgToolArea proc uses edi esi
hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
mov edi,lpti
mov esi,lprect
assume esi:ptr RECT
assume edi:ptr TOOLINFO
.if id==0
mov [edi].rect.left,0
mov [edi].rect.top,0
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
mov [edi].rect.bottom,eax
.elseif id==1
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
inc eax
mov [edi].rect.left,eax
mov [edi].rect.top,0
mov eax,[esi].right
sub eax,[esi].left
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.elseif id==2
mov [edi].rect.left,0
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
inc eax
mov [edi].rect.top,eax
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.else
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
inc eax
mov [edi].rect.left,eax
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
inc eax
mov [edi].rect.top,eax
mov eax,[esi].right
sub eax,[esi].left
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.endif
push lpText
pop [edi].lpszText
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
assume edi:nothing
assume esi:nothing
ret
SetDlgToolArea endp
end start
Анализ:
После того, как создано основное диалоговое окно, мы создает тултип-контрол
функцией CreateWindowsEx.
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
TTS_ALWAYSTIp,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInstance,NULL
mov hwndTool,eax
После этого мы переходим к определению четырех tool'ов для каждого угла
диалогового окна.
mov id,0 ; used as the tool ID
mov ti.cbSize,sizeof TOOLINFO
mov ti.uFlags,TTF_SUBCLASS ; tell the tooltip control to subclass the dialog window.
push hDlg
pop ti.hWnd ; handle to the window that contains the tool
invoke GetWindowRect,hDlg,addr rect ; получаем размерность клиентской
; области
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
Мы инициализируем члены структуры TOOLINFO. Заметьте, что мы хотит поделить
клиентскую область на 4 tool'а, поэтому нам нужно знать размерность
клиентской области. Это то, для чего мы вызываем GetWindowsRect. Мы не хотим
передавать сообщения мыши тултипу, поэтому мы указываем
флага TIF_SUBCLASS.
SetDlgToolArea - это функция, которая высчитывает координаты прямоугольной
области каждого tool'а и регистрирует tool в тултипе. Я не хочу
вдаваться в
подробности относительно этого, достаточно сказать, что она делит клиентскую
область на 4 области с одними и теми же размерами. Затем она посылает
сообщение TTN_ADDTOOL тултипу, передавая адрес структуры TOOLINFO в
lparam.
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
После того, как все 4 tool' а зарегистрированы, мы можем перейти к кнопкам
на диалоговом окне. Мы можем обрабатывать каждую кнопку с
помошью ее ID, но
это утомительно. Вместо этого мы используем EnumChildWindows
ApI, чтобы
перечислить все контролы на диалоговом окне и затем зарегистрировать для
каждого из них подсказку. EnumChildWindows имеет следующий синтаксис:
EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lparam:DWORD
hWnd - хэндл родительского окна. lрEnumFunc - адрес функции
EnumChildproc,
которая будет вызываться для каждого перечисленного контрола.
lparam -
заданное приложением значение, которое будет передано
EnumChildproc. У этой
функции следующее определение:
EnumChildproc proto hwndChild:DWORD, lparam:DWORD
hwndChild - хэндл контрола, найденного EnumChildWindows.
lparam - это тоже
значение, что вы передали EnumChildWindow. В нашем примере мы вызываем
EnumChildWindows следующим образом:
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
Мы передаем адрес структуры TOOLINFO в параметре
lparam, потому что мы
будем регистрировать подсказки для каждого дочерний контрол в функции
EnumChild. Если мы не будем использовать данный метода, нам придется объявить
глобальную переменную, чтобы предотвратить баги.
Когда мы вызываем EnumchildWindows, Windows перечислит дочерние конролы
нашего диалогового окна и вызовет для каждого из ни
функцию Enumchild. То
есть, если наше диалоговое окно имеет два контрола, EnumChild будет вызван
дважды.
Функция EnumChild заполнит соответствующие поля структуры TOOLINFO и
зарегистрирует tool.
EnumChild proc uses edi hwndChild:DWORD,lparam:DWORD
LOCAL buffer[256]:BYTE
mov edi,lparam
assume edi:ptr TOOLINFO
push hwndChild
pop [edi].uId ; we use the whole client area of the control as the tool
or [edi].uFlags,TTF_IDISHWND
invoke GetWindowText,hwndChild,addr buffer,255
lea eax,buffer ; use the window text as the tooltip text
mov [edi].lpszText,eax
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
assume edi:nothing
ret
EnumChild endp
Заметьте, что в этом случае мы используем другой тип tool'ов, покрывающий
всю клиентскую область окна. Поэтому нам нужно заполнить поле uID хэндлом
окна, которое содержит tool. Также мы указываем флаг TTF_IDISHWND в
параметре uFlags.