На этом шаге мы рассмотрим реализацию всплывающих подсказок.
Здесь мы рассмотрим весьма интересный вопрос о всплывающих подсказках. В визуальных языках программирования всплывающие подсказки организуются посредством установки соответствующих свойств объектов, расположенных на объекте-контейнере. Наша с вами задача - разработать механизм, позволяющий без каких-либо дополнительных библиотек устанавливать подсказки на любые объекты, расположенные в окне. Итак, приступаем.
Прежде всего заметим, что всплывающая подсказка - это всего лишь окно с определенными свойствами. Вот эти свойства: DS_3DLOOK, WS_POPUP, WS_VISIBLE, WS_BORDER. В принципе можно экспериментировать - добавлять или удалять свойства. Но без одного свойства вы никак не обойдетесь - это WS_POPUP. Собственно POPUP можно перевести как поплавок. Кроме того, определение всплывающего окна в файле ресурсов не должно содержать опции CAPTION.
Появление подсказки не должно менять ситуацию в диалоговом окне. Это значит - вызов подсказки должен быть немодальным, при помощи функции CreateDialogIndirect. Кроме того, следует предусмотреть переустановку фокуса на диалоговое окно. Для этого достаточно в нужном месте вызвать функцию SetFocus.
Итак, подсказка - это диалоговое окно, и, следовательно, оно должно иметь свою функцию. Что должна содержать эта функция? По крайней мере, обработку трех событий: WM_INITDIALOG, WM_PAINT, WM_TIMER. По получении сообщения WM_INITDIALOG следует определить размер и положение подсказки. Кроме того, если мы предполагаем, что подсказка должна спустя некоторое время исчезать, следует установить таймер. По получении сообщения WM_PAINT следует вывести в окно подсказки текст. Если определять размер окна подсказки точно по строке выводимого текста, то цвет фона подсказки будет полностью определяться цветом выводимого текста. Наконец по приходе сообщения WM_TIMER мы закрываем подсказку.
С самой подсказкой более или менее ясно. Определимся теперь, как и где будет вызываться эта подсказка. Реализуем такой подход: в основном диалоговом окне определяем таймер, в функции которого будет проверяться положение курсора. В зависимости от этого положения и будет вызываться или удаляться подсказка. В функции таймера нужно предусмотреть:
В следующем листинге приведена программа, которая демонстрирует описанный выше подход. На рисунке 1 представлено диалоговое окно с подсказками. В принципе, рассмотренный подход не является единственным, и, разобравшись в нем, вы можете пофантазировать и придумать свои способы создания подсказок.
Файл pr62_1.rc:
//Определение констант. #define WS_SYSMENU 0x00080000L //Элементы на окне должны быть изначально видимы. #define WS_VISIBLE 0x10000000L //Бордюр вокруг элемента. #define WS_BORDER 0x00800000L //Возможность фокусировать элемент //при помощи клавиши TAB. #define WS_TABSTOP 0x00010000L //Текст в окне редактирования прижат к левому краю. #define ES_LEFT 0x0000L //Стиль всех элементов на окне. #define WS_CHILD 0x40000000L //Стиль - кнопка. #define BS_PUSHBUTTON 0x00000000L //Центрировать текст на кнопке. #define BS_CENTER 0x00000300L //Тип окна - "поплавок". #define WS_POPUP 0x80000000L //Стиль - диалоговое окно Windows 95. #define DS_3DLOOK 0x0004L //Определение диалогового окна. DIAL1 DIALOG 0, 0, 240, 100 STYLE WS_SYSMENU | DS_3DLOOK CAPTION "Окно с всплывающими подсказками" FONT 8, "Arial" { //Окно редактирования, идентификатор 1. CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 100, 5, 130, 12 //Кнопка, идентификатор 2. CONTROL "Выход", 2, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 76, 50, 14 } //Диалоговое окно подсказки. HINTW DIALOG 0, 0, 240, 8 STYLE DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_BORDER FONT 8, "MS Sans Serif" { }
Файл pr62_1.inc:
;Константы. ;Цвет фона окна подсказки. RED = 255 GREEN = 255 BLUE = 150 RGBB equ (RED or (GREEN shl 8)) or (BLUE shl 16) ;Цвет текста окна подсказки. RED = 20 GREEN = 20 BLUE = 20 RGBT equ (RED or (GREEN shl 8)) or (BLUE shl 16) ;Сообщение приходит при закрытии окна. WM_CLOSE equ 10h WM_INITDIALOG equ 110h ;Сообщение приходит при событии с элементом на окне. WM_COMMAND equ 111h ;Сообщение от таймера. WM_TIMER equ 113h ;Сообщение посылки текста элементу. WM_SETTEXT equ 0Ch WM_PAINT equ 0Fh ;Прототипы внешних процедур. IFDEF MASM EXTERN CreateDialogParamA@20:NEAR EXTERN SetFocus@4:NEAR EXTERN lstrcpyA@8:NEAR EXTERN DestroyWindow@4:NEAR EXTERN lstrlenA@4:NEAR EXTERN GetDlgItem@8:NEAR EXTERN GetCursorPos@4:NEAR EXTERN TextOutA@20:NEAR EXTERN SetBkColor@8:NEAR EXTERN SetTextColor@8:NEAR EXTERN BeginPaint@8:NEAR EXTERN EndPaint@8:NEAR EXTERN GetTextExtentPoint32A@16:NEAR EXTERN MoveWindow@24:NEAR EXTERN GetWindowRect@8:NEAR EXTERN ReleaseDC@8:NEAR EXTERN GetDC@4:NEAR EXTERN SendDlgItemMessageA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SetTimer@16:NEAR EXTERN KillTimer@8:NEAR ELSE EXTERN CreateDialogParamA:NEAR EXTERN SetFocus:NEAR EXTERN lstrcpyA:NEAR EXTERN DestroyWindow:NEAR EXTERN lstrlenA:NEAR EXTERN GetDlgItem:NEAR EXTERN GetCursorPos:NEAR EXTERN TextOutA:NEAR EXTERN SetBkColor:NEAR EXTERN SetTextColor:NEAR EXTERN BeginPaint:NEAR EXTERN EndPaint:NEAR EXTERN GetTextExtentPoint32A:NEAR EXTERN MoveWindow:NEAR EXTERN GetWindowRect:NEAR EXTERN ReleaseDC:NEAR EXTERN GetDC:NEAR EXTERN SendDlgItemMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN SetTimer:NEAR EXTERN KillTimer:NEAR CreateDialogParamA@20 = CreateDialogParamA SetFocus@4 = SetFocus lstrcpyA@8 = lstrcpyA DestroyWindow@4 = DestroyWindow lstrlenA@4 = lstrlenA GetDlgItem@8 = GetDlgltem GetCursorPos@4 = GetCursorPos TextOutA@20 = TextOutA SetBkColor@8 = SetBkColor SetTextColor@8 = SetTextColor BeginPaint@8 = BeginPaint EndPaint@8 = EndPaint GetTextExtentPoint32A@16 = GetTextExtentPoint32A MoveWindow@24 = MoveWindow GetWindowRect@8 = GetWindowRect ReleaseDC@8 = ReleaseDC GetDC@4 = GetDC SendDlgItemMessageA@20 = SendDlglteroMessageA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA DialogBoxPararaA@20 = DialogBoxParamA EndDialog@8 = EndDialog SetTimer@16 = SetTimer KillTimer@8 = KillTimer ENDIF ;Структуры. ;Структура сообщения. MSGSTRUCT STRUC MSHWND DD ? ;Идентификатор окна, получающего сообщение. MSMESSAGE DD ? ;Идентификатор сообщения. MSWPARAM DD ? ;Доп. информация о сообщении. MSLPARAM DD ? ;Доп. информация о сообщении. MSTIME DD ? ;Время посылки сообщения. MSPT DD ? ;Положение курсора во время посылки сообщения. MSGSTRUCT ENDS ;Cтруктура размера окна. RECT STRUC L DD ? T DD ? R DD ? B DD ? RECT ENDS ;Структура координаты точки. SIZ STRUC X DD ? Y DD ? SIZ ENDS ;Структура для BeginPaint. PAINTSTR STRUC hdc DWORD 0 fErase DWORD 0 left DWORD 0 top DWORD 0 right DWORD 0 bottom DWORD 0 fRes DWORD 0 flncUp DWORD 0 Reserv DB 32 dup(0) PAINTSTR ENDS ;Структура для получения позиции курсора POINT STRUC X DD ? Y DD ? POINT ENDS
Файл pr62_1.asm:
.386P ;Плоская модель. .MODEL FLAT, STDCALL include pr62_1.inc ;Директивы компоновщику для подключения библиотек. IFDEF MASM ;Для компоновщика LINK.EXE. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib ELSE ;Для компоновщика TLINK32.EXE. includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ;Дескриптор приложения. PA DB 'DIAL1',0 HIN DB "HINTW",0 XX DD ? YY DD ? ;------------------------------------- R1 RECT <?> R2 RECT <?> S SIZ <?> PS PAINTSTR <?> PT POINT <?> ;Ддескрипторы окон-подсказок для первого и второго элемента. H1 DD 0 H2 DD 0 ;Строка-подсказка. HINTS DB 60 DUP(?) ;Перечень подсказок. HINT1 DB "Редактирование строки",0 HINT2 DB "Кнопка выхода",0 ;Для временного хранения контекста устройства. DC DD ? ;Счетчик. P1 DD ? _DATA ENDS ;Сегмент кода. _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ;Получить дескриптор приложения. PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;------------------------------------- ;Создать диалоговое окно. PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ;Здесь можно разместить сообщение об ошибке. KOL: ;------------------------------------- PUSH 0 CALL ExitProcess@4 ;------------------------------------- ;------------------------------------- ;Процедура окна. ;Расположение параметров в стеке: ;[ЕВР+014Н] LPARAM ;[ЕВР+10Н] WAPARAM ;[ЕВР+0СН] MES ;[ЕВР+8] HWND WNDPROC PROC PUSH EBP MOV EBP, ESP PUSH EBX PUSH ESI PUSH EDI ;------------------------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ;Здесь реакция на закрытие окна. ;Удалить таймер. L4: PUSH 2 ;Идентификатор таймера. PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 ;Закрыть диалог. PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ;Здесь начальная инициализация. ;Установить таймер. PUSH OFFSET TIMPROC ;Параметр = NULL PUSH 500 ;Интервал 0.5 с PUSH 2 ;Идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE L3 ;Кнопка выхода? CMP WORD PTR [EBP+10H],2 JNE L3 JMP L4 L3: FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP ;------------------------------------- ;Процедура таймера. ;Расположение параметров в стеке. ;[ЕВР+014Н] LPARAM - промежуток запуска Windows. ;[ЕВР+10Н] WAPARAM - идентификатор таймера. ;[EBP+OCH] WM_TIMER ;[ЕВР+8] HWND TIMPROC PROC PUSH EBP MOV EBP,ESP ;Получить положение курсора. PUSH OFFSET PT CALL GetCursorPos@4 ;Запомнить координаты. MOV EAX,PT.X MOV XX, EAX MOV EAX,PT.Y MOV YY,EAX ;Получить положение элементов. ;Окно редактирования. PUSH 1 PUSH DWORD PTR [EBP+08H] CALL GetDlgItem@8 PUSH OFFSET R1 PUSH EAX CALL GetWindowRect@8 ;Кнопка выхода. PUSH 2 PUSH DWORD PTR [EBP+08H] CALL GetDlgItem@8 PUSH OFFSET R2 PUSH EAX CALL GetWindowRect@8 ;Увеличить счетчик. INC P1 MOV ECX,XX MOV EDX,YY ;Проверка условий. .IF H1==0 && P1>5 .IF EDX<=R1.B && EDX>=R1.T && ECX>=R1.L && ECX<=R1.R ;Подготовить строку. PUSH OFFSET HINT1 PUSH OFFSET HINTS CALL lstrcpyA@8 ;Создать диалоговое окно - подсказку PUSH 0 PUSH OFFSET HINT PUSH DWORD PTR [EBP+08H] PUSH OFFSET HIN PUSH [HINST] CALL CreateDialogParamA@20 MOV H1,EAX ;Установить фокус. PUSH DWORD PTR [EBP+08H] CALL SetFocus@4 ;Обнулить счетчик. MOV P1,0 JMP _END .ENDIF .ENDIF .IF H1!=0 .IF (EDX>R1.B || EDX<R1.T) || (ECX<R1.L || ECX>R1.R) ;Удаление подсказки в связи с перемещением курсора. PUSH H1 CALL DestroyWindow@4 MOV H1,0 JMP _END .ENDIF .ENDIF .IF H2==0 && P1>5 .IF EDX<=R2.B && EDX>=R2.T && ECX>=R2.L && ECX<=R2.R ;Подготовить строку. PUSH OFFSET HINT2 PUSH OFFSET HINTS CALL lstrcpyA@8 ;Сздать диалоговое окно - подсказку. PUSH 0 PUSH OFFSET HINT PUSH DWORD PTR [EBP+08H] PUSH OFFSET HIN PUSH [HINST] CALL CreateDialogParamA@20 MOV H2,EAX ;Установить фокус. PUSH DWORD PTR [EBP+08H] CALL SetFocus@4 ;Обнулить счетчик. MOV P1,0 JMP _END .ENDIF .ENDIF .IF H2!=0 .IF (EDX>R2.B || EDX<R2.T) || (ECX<R2.L || ECX>R2.R) ;Удаление подсказки в связи с перемещением курсора. PUSH H2 CALL DestroyWindow@4 MOV H2,0 JMP _END .ENDIF .ENDIF ;Восстановить стек. _END: POP EBP RET 16 TIMPROC ENDP ;------------------------------------- ;Процедура окна всплывающей подсказки HINT PROC PUSH EBP MOV EBP,ESP CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE NO_INIT ;Инициализация. ;Получить контекст. PUSH DWORD PTR [EBP+08H] CALL GetDC@4 MOV DC,EAX ;Получить длину строки. PUSH OFFSET HINTS CALL lstrlenA@4 ;Получить длину и ширину строки. PUSH OFFSET S PUSH EAX PUSH OFFSET HINTS PUSH DC CALL GetTextExtentPoint32A@16 ;Установить положение и размер окна-подсказки. PUSH 0 PUSH S.Y ADD S.X,2 PUSH S.X SUB YY,20 PUSH YY ADD XX,10 PUSH XX PUSH DWORD PTR [EBP+08H] CALL MoveWindow@24 ;Закрыть контекст PUSH DC PUSH DWORD PTR [EBP+08H] CALL ReleaseDC@8 ;Установить таймер. PUSH 0 PUSH 6000 ;Интервал 6 с. PUSH 3 ;Идентификатор таймера. PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FIN NO_INIT: CMP DWORD PTR [EBP+0CH],WM_PAINT JNE NO_PAINT ;Перерисовка окна. ;Получить контекст. PUSH OFFSET PS PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 MOV DC,EAX ;Установить цвета фона и текста подсказки. PUSH RGBB PUSH EAX CALL SetBkColor@8 PUSH RGBT PUSH DC CALL SetTextColor@8 ;Вывести текст. PUSH OFFSET HINTS CALL lstrlenA@4 PUSH EAX PUSH OFFSET HINTS PUSH 0 PUSH 0 PUSH DC CALL TextOutA@20 ;Закрыть контекст. PUSH OFFSET PS PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 JMP FIN NO_PAINT: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FIN ;Обработка события таймера. ;Удалить таймер и удалить диалоговое окно. ;Подсказка удаляется в связи с истечением срока 6 с. PUSH 3 PUSH DWORD PTR [EBP+08H] CALL KillTimer@8 PUSH DWORD PTR [EBP+08H] CALL DestroyWindow@4 FIN: POP EBP RET 16 HINT ENDP _TEXT ENDS END START
Результат работы приложения изображен на рисунке 1:
Рис.1. Результат работы приложения
Прокомментируем приведенную программу.
Прежде всего обратим ваше внимание, что в данной программе мы используем условные конструкции времени выполнения. Данный шаг вполне закономерен и обусловлен только необходимостью несколько сократить объем, а также упростить читаемость программы. Вложенность условных конструкций, а также расстановка скобок вызваны желанием сократить длину строки и одновременно необходимостью транслировать программу как с помощью MASM32, так и с помощью TASM32. Напомним, что два ассемблера весьма сильно отличаются, когда речь идет о макросредствах.
Трансляция программы:
ML /с /coff /DMASM pr62_1.asm RC pr62_1.rc LINK /SUBSYSTEM:WINDOWS pr62_1.obj pr62_1.res
TASM32 /ml pr62_1.asm BRCC32 pr62_1.rc TLINK32 -aa pr62_1.obj,,,,,pr62_1.res
Как вы, наверное, уже поняли, процедура таймера проверяет каждые 0.5 с положение курсора. Если курсор находится на элементе (окне редактирования или кнопке) и подсказка еще не вызвана (H1 или Н2 отлично от нуля), то вызывается подсказка. При этом учитывается еще величина счетчика (Р1), чтобы подсказка появлялась с некоторой задержкой. Если при очередном вызове процедуры окажется, что курсор находится уже вне элемента, а подсказка еще на экране, то она удаляется. Данный механизм не учитывает случай, когда курсор быстро переходит от одного элемента к другому. В этом случае вероятна ситуация, когда на экране окажутся две подсказки! Впрочем, первая подсказка должна тут же исчезнуть.
В нашей программе на диалоговом окне расположено всего два элемента: окно редактирования и кнопка. Мы хотели показать, что в принципе не имеет значения, какой элемент: для любого из них может быть установлена подсказка. Положение подсказки по отношению к курсору легко регулируется, и вы можете сами менять его.
Функция GetCursorPos получает положение курсора в абсолютных координатах относительно экрана. Здесь не возникает проблем, т. к. функция GetWindowsRec также получает положение элемента окна в абсолютных координатах. Предварительно нам приходится определять дескриптор окна при помощи функции GetDlgItem.
Со следующего шага мы начнем рассматривать многозадачное программирование.