СТАТЬИ > Уроки Iczelion'а

Win32 API. Урок 27. Тултип-контрол

Мы изучим контроль tooltip. Что это такое, как его создать и как им пользоваться.

Сырец довнлоад здесь.

ТЕОРИЯ

Тултип - это маленькая прямоугольное окно, которое отображается, когда курсор мыши находится над какой-то определенной областью. Окно тултипа содержит текст, заданный программистом. В этом отношении тултип играет ту же роль, что и окно статуса, но оно исчезает, когда пользователь кликает или убирает курсор мыши из заданной области. Вы, вероятно, знакомы с тултипами, ассоциированные с кнопками тулбара. Эти "тултипы" - одно из удобств, предоставляемых тулбаром. Если вам нужны тултипы для других окон/контролов, вам необходимо создать собственный тултип контрол.

Теперь, когда вы знаете, что такое тултип, давайте перейдем к тому, как мы можем создать и использовать его. Ниже расписаны шаги:

Ниже мы детально проанализируем каждый шаг.

Создание тултипа

Тултип - это 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_ALWAYSTIP. Этот стиль указывает, что тултип будет показываться, когда курсор мыши будет находиться над заданной областью вне зависимости от статуса окна. То есть, если вы будете использовать этот флга, тултип будет появляться (когда курсор мыши будет находиться над определенной областью), даже если окно, с которым ассоциирован тултип, неактивно.

Вам не нужно задавать слили 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

Резюме: вы должны заполнить структуру TOOLINFO и передать ее тултипу. Эта структура задаст характерситики tool'а.

Регистрация tool'а

После того, как вы заполнили структуру TOOLINFO, вы должны передать ее тултипу. Тултип может обслуживать много tool'ов, поэтому обычно одно тултипа хватает на все окно. Чтобы зарегистрировать tool, вы посылаете тултипу сообщение TTM_ADDTOOL. wParam не используется, а lParam должен содержать адрес структуры 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. lParam должен содержать адрес структуры 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, чтобы перехватывать сообщения от мыши без участия сабклассированного окна. Этот метод проще использовать и он требует меньше усилий, так как тултип берет всю обработку сообщений на себя.

Вот и все. Теперь ваш тултип полностью функционален. Есть несколько полезных тултиповых сообщений, о которых вас следует знать.

ПРИМЕР

Следующий пример - это простое диалоговое окно с двумя кнопками. Клиентская область диалогового окна поделена на 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 - хэндл родительского окна. lpEnumFunc - адрес функции 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.

  [C] Iczelion, пер. Aquila

© 2002-2004 wasm.ru - all rights reserved and reversed