Assembler для Windows

досуг с проститутки в новосибирске girls-nsk.cc



Часть III. Более сложные примеры программирования в Windows

Глава 1. Примеры программ, использующих таймер

Таймер является одним из мощных инструментов, предоставляемых операционной системой и позволяющих решать самые разнообразные задачи. С таймером Вы познакомились, когда занимались консольными приложениями. Там мы пользовались функциями timeSetEvent и timeKillEvent. Для консольных приложений это очень удобные функции. В оконных приложениях чаще используют функции SetTimer и KillTimer. Особенность таймера, создаваемого функцией SetTimer, заключается в том, что сообщение WM_TIMER, которое начинает посылать система приложению после выполнения функции SetTimer, приходит со всеми другими сообщениями наравне, на общих основаниях. Следовательно, интервал между двумя приходами сообщения WM_TIMER может несколько варьироваться. В большинстве случаев это не существенно.

У сообщения таймера есть еще одна особенность. Если система посылает сообщение приложению, а предыдущее сообщение еще стоит в очереди, то система объединяет эти два сообщения. Таким образом, "вынужденный простой" не приводит к приходу на приложение подряд нескольких сообщений таймера.

Вот те задачи, которые можно решить с помощью таймера.

  1. Отслеживание времени: секундомер, часы и т.д. Нарушение периодичности не имеет значения, так как по приходе сообщения время можно отследить, вызвав функцию получения системного времени.
  2. Таймер - один из способов осуществления многозадачности. Можно установить сразу несколько таймеров на разные функции, в результате периодически будет исполняться то одна, то другая функция. Более подробно о многозадачности будет сказано в следующей главе.
  3. Периодический вывод на экран обновленной информации.
  4. Автосохранение - функция особенно полезная для редакторов.
  5. Задание темпа изменения каких-либо объектов на экране.
  6. Мультипликация - по приходе сообщения от таймера обновляется графическое содержимое экрана или окна, так что возникает эффект мультипликации.

Рассмотрим, как нужно обращаться с функцией SetTimer. Вот параметры этой функции.

  • 1-й параметр - дескриптор окна, с которым ассоциируется таймер. Если этот параметр сделать равным NULL (0), то будет проигнорирован и второй параметр.
  • 2-й параметр - определяет идентификатор таймера.
  • 3-й параметр - определяет интервал посылки сообщения WM_TIMER.
  • 4-й параметр - определяет адрес функции, на которую будет приходить сообщение WM_TIMER. Если параметр равен NULL, то сообщение будет приходить на функцию окна.

Если функция выполнилась успешно, то возвращаемым значением будет являться идентификатор таймера, который, естественно, будет совпадать со вторым параметром, если первый параметр будет отличным от NULL. В случае неудачи функция возвратит ноль.

Из сказанного следует, что функция может быть вызвана тремя способами:

  1. Задан дескриптор окна, а четвертый параметр задается равным нулю.
  2. Задан дескриптор окна, а четвертый параметр определяет функцию, на которую будет приходить сообщение WM_TIMER.
  3. Дескриптор окна равен NULL, а четвертый параметр определяет функцию, на которую будет приходить сообщение WM_TIMER. Идентификатор таймера в этом случае будет определяться по возвращаемому функцией значению.

Функция, на которую приходит сообщение WM_TIMER, имеет следующие параметры:

  • 1-й параметр - дескриптор окна, с которым ассоциирован таймер.
  • 2-й параметр - сообщение WM_T1MER.
  • 3-й параметр - идентификатор таймера.
  • 4-й параметр - время в миллисекундах, которое прошло с момента запуска Windows.

Функция KillTimer удаляет созданный параметр и имеет следующие параметры:

  • 1-й параметр - дескриптор окна.
  • 2-й параметр - идентификатор таймера.

I

Первый пример, рассматриваемый в данном разделе, представляет простейший пример таймера. Таймер отсчитывает десять тиков и закрывает диалоговое окно, выдавая MessageBox с сообщением об окончании работы программы. Данная программа являет собой пример организации таймера на базе самой функции окна.

Заметим, что начиная с данной главы большинство приводимых программ могут транслироваться и в пакете MASM32, и в пакете TASM32 без всяких изменений.

// файл timer.rc
// определение констант
#define WS_SYSMENU 0x00080000L
#define WS_MINIMIZEBOX 0x00020000L
#define WS_MAXIMIZEBOX 0x00010000L
// стиль - кнопка
#define BS_PUSHBUTTON 0x00000000L
// кнопка в окне должна быть видимой
#define WS_VISIBLE 0x10000000L
// центрировать текст на кнопке
#define BS_CENTER 0x00000300L
// стиль кнопки
#define WS_CHILD 0х40000000L
// возможность фокусировать элемент
// при помощи клавиши TAB
#define WS_TABSTOP 0x00010000L
#define DS_3DLOOK 0x0004L
//определение диалогового окна
DIAL1 DIALOG 0, 0, 240, 120
STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK
CAPTION "Пример диалогового окна с таймером"
FONT 8, "Arial"
{
// кнопка, идентификатор 5
CONTROL "Выход", 5, "button", BS_PUSHBUTTON
| BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
180, 76, 50, 14
}
; файл timer.inc
; константы
; сообщение приходит при закрытии окна
WM_CLOSE equ 10h
WM_INITDIALOG equ 110h
WM_COMMAND equ 111h
WM_TIMER equ 113h
; прототипы внешних процедур
IFDEF MASM
EXTERN ReleaseDC@8:NEAR
EXTERN GetDC@4:NEAR
EXTERN TextOutA@20:NEAR
EXTERN MessageBoxA@16:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SetTimer@16:NEAR
EXTERN KillTimer@8:NEAR
ELSE
EXTERN ReleaseDC:NEAR
EXTERN GetDC:NEAR
EXTERN TextOutA:NEAR
EXTERN MessageBoxA:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetModuleHandleA:NEAR
EXTERN DialogBoxParamA:NEAR
EXTERN EndDialog:NEAR
EXTERN SendMessageA:NEAR
EXTERN SetTimer:NEAR
EXTERN KillTimer:NEAR
ReleaseDC@8 = ReleaseDC
GetDC@4 = GetDC
TextOutA@20 = TextOutA
MessageBoxA@16 = MessageBoxA
ExitProcess@4 = ExitProcess
GetModuleHandleA@4 = GetModuleHandleA
DialogBoxParamA@20 = DialogBoxParamA
EndDialog@8 = EndDialog
SendMessageA@16 = SendMessageA
SetTimer@16 = SetTimer
KillTimer@8 = KillTimer
ENDIF
; структуры
; структура сообщения
MSGSTRUCT STRUC
MSHWND DD ?
MSMESSAGE DD ?
MSWPARAM DD ?
MSLPARAM DD ?
MSTIME DD ?
MSPT DD ?
MSGSTRUCT ENDS
; файл timer.asm
.386P
; плоская модель
.MODEL FLAT, stdcall
include timer.inc
; директивы компоновщику для подключения библиотек
IFDEF MASM
; для компоновщика LINK.EXE
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\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
COUNT DD 0
TEXT DB 0
CAP DB 'Сообщение',0
MES DB 'Выход по таймеру',0
_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 ;---------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;--------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; здесь реакция на закрытие окна L3: ; удалить таймер PUSH 1 ; идентификатор таймера 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 L5 ; здесь начальная инициализация ; установить таймер PUSH 0 ; параметр = NULL PUSH 1000 ; интервал 1 с. PUSH 1 ; идентификатор таймера PUSH DWORD PTR [EBP+08H] CALL SetTimer@16 JMP FINISH L5: CMP DWORD PTR [EBP+0CH], WM_COMMAND JNE L2 ; кнопка выхода? CMP WORD PTR [EBP+10H],5 JNE FINISH JMP L3 L2: CMP DWORD PTR [EBP+0CH],WM_TIMER JNE FINISH ; не пора ли заканчивать ? CMP COUNT,9 ; выход без предупреждения JA L3 ; выход через сообщение JE L4 ; пришло сообщение таймера ; подготовить текст MOV EAX, COUNT ADD EAX,49 MOV TEXT, AL ; получить контекст PUSH DWORD PTR [EBP+08H] CALL GetDC@4 ; запомнить контекст PUSH EAX ; вывести значение счетчика PUSH 1 PUSH OFFSET TEXT PUSH 10 PUSH 10 PUSH EAX CALL TextOutA@20 ; удалить контекст POP EAX PUSH EAX PUSH DWORD PTR [EBP+08H] CALL ReleaseDC@8 ; увеличить счетчик INC COUNT JMP FINISH L4: INC COUNT ; сообщение о выходе по таймеру PUSH 0 PUSH OFFSET CAP PUSH OFFSET MES PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 JMP L3 FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX,0 RET 16 WNDPROC ENDP ;------------------------------------------------- _TEXT ENDS END START

Рис. 3.1.1 Пример реализации простейшего таймера.

Прокомментируем программу на Рис. 3.1.1. Организация таймера здесь проста и очевидна. Единственное, что может вызвать трудность понимания, это то, как удается оставить MessageBox и одновременно закрыть диалоговое окно. Но здесь тоже все достаточно просто: сообщение появляется при COUNT=9, а когда приходит следующее сообщение, то COUNT уже больше 9, и выполняется ветка закрытия диалогового окна.

Трансляция программы.
Пакет MASM32.

ML /c /coff /DMASM timer.asm
RC timer.rc
LINK /SUBSYSTEM:WINDOWS timer.obj timer.res
Пакет TASM32.
TASM32 /ml timer.asm
BRCC32 timer.rc
TLINK32 -aa timer.obj,,,,,timer.res

II

Следующая программа несколько сложнее предыдущей. Здесь действуют два таймера. Можно считать, что запускаются одновременно две задачи39. Одна задача с периодичностью 0.5 сек. получает системное время и формирует строку для вывода (STRCOPY). Эта задача имеет свою собственную функцию, на которую приходит сообщение WM_TIMER. Вторая задача работает в рамках функции окна. Эта задача с периодичностью 1 сек. выводит время и дату в окно редактирования, расположенное на диалоговом окне. Таким образом, две задачи взаимодействуют друг с другом посредством глобальной переменной STRCOPY.

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

// файл timer2.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 ES_READONLY 0x0800L
#define DS_3DLOOK 0x0004L
// определение диалогового окна
DIAL1 DIALOG 0, 0, 240, 100
STYLE WS_SYSMENU | DS_3DLOOK
CAPTION "Диалоговое окно с часами и датой"
FONT 8, "Arial"
{
CONTROL "", 1, "edit", ES_LEFT | WS_CHILD
| WS_VISIBLE | WS_BORDER
| WS_TABSTOP | ES_READONLY, 100, 5, 130, 12
}
; файл timer2.inc
; константы
; сообщение приходит при закрытии окна
WM_CLOSE equ 10h
;сообщение приходит при создании окна
WM_INITDIALOG equ 110h
;сообщение приходит при событии с элементом на окне
WM_COMMAND equ 111h
;сообщение от таймера
WM_TIMER equ 113h
; сообщение посылки текста элементу
WM_SETTEXT equ 0Ch
; прототипы внешних процедур
IFDEF MASM
EXTERN SendDlgItemMessageA@20:NEAR
EXTERN wsprintfA:NEAR
EXTERN GetLocalTime@4: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 SendDlgItemMessageA:NEAR
EXTERN _wsprintfA:NEAR
EXTERN GetLocalTime:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetModuleHandleA:NEAR
EXTERN DialogBoxParamA:NEAR
EXTERN EndDialog:NEAR
EXTERN SetTimer:NEAR
EXTERN KillTimer:NEAR
SendDlgItemMessageA@20 = SendDlgItemMessageA
wsprintfA = _wsprintfA
GetLocalTime@4 = GetLocalTime
ExitProcess@4 = ExitProcess
GetModuleHandleA@4 = GetModuleHandleA
DialogBoxParamA@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
; структура данных дата-время
DAT STRUC
year DW ?
month DW ?
dayweek DW ?
day DW ?
hour DW ?
min DW ?
sec DW ?
msec DW ?
DAT ENDS
; файл timer2.asm
.386P
; плоская модель
.MODEL FLAT, stdcall
include timer2.inc
; директивы компоновщику для подключения библиотек
IFDEF MASM
; для компоновщика LINK.EXE
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\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
TIM DB "Дата %u/%u/%u Время %u:%u:%u",0
STRCOPY DB 50 DUP (?)
DATA DAT <0>
_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
;--------------------------------------
; процедура окна
; расположение параметров в стеке
; [EBP+014Н] ; LPARAM
; [EBP+10Н] ; WAPARAM
; [EBP+0CH] ; MES
; [EBP+8] ; HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
;-----------------
CMP DWORD PTR [EBP+0CH],WM_CLOSE
JNE L1
; здесь реакция на закрытие окна
; удалить таймер 1
PUSH 1 ; идентификатор таймера
PUSH DWORD PTR [EBP+08H]
CALL KillTimer@8
; удалить таймер 2
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
; здесь начальная инициализация
; установить таймер 1
PUSH 0 ; параметр = NULL
PUSH 1000 ; интервал 1 с.
PUSH 1 ; идентификатор таймера
PUSH DWORD PTR [EBP+08H]
CALL SetTimer@16
; установить таймер 2
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_TIMER
JNE FINISH
; отправить строку в окно
PUSH OFFSET STRCOPY
PUSH 0
PUSH WM_SETTEXT
PUSH 1 ; идентификатор элемента
PUSH DWORD PTR [EBP+08H]
CALL SendDlgItemMessageA@20
FINISH:
POP EDI
POP ESI
POP EBX
POP EBP
MOV EAX,0
RET 16
WNDPROC ENDP
;--------------------------------
;процедура таймера
; расположение параметров в стеке
; [EBP+014Н] ; LPARAM - промежуток запуска Windows
; [EBP+10Н] ; WAPARAM - идентификатор таймера
; [EBP+0CH] ; WM_TIMER
; [EBP+8] ; HWND
TIMPROC PROC
PUSH EBP
MOV EBP,ESP
; получить локальное время
PUSH OFFSET DATA
CALL GetLocalTime@4
; получить строку для вывода даты и времени
MOVZX EAX,DATA.sec
PUSH EAX
MOVZX EAX,DATA.min
PUSH EAX
MOVZX EAX,DATA.hour
PUSH EAX
MOVZX EAX,DATA.year
PUSH EAX
MOVZX EAX,DATA.month
PUSH EAX
MOVZX EAX,DATA.day
PUSH EAX
PUSH OFFSET TIM
PUSH OFFSET STRCOPY
CALL wsprintfA
; восстановить стек
ADD ESP,32
POP EBP
RET 16
TIMPROC ENDP
_TEXT ENDS
END START

Puc. 3.1.2. Пример использования двух таймеров.

Трансляция программы.
Пакет MASM32.

ML /c /coff /DMASM timer2.asm
RC timer2.rc
LINK /SUBSYSTEM:WINDOWS timer2.obj timer2.res
Пакет TASM32.
TASM32 /ml timer2.asm
BRCC32 timer2.rc
TLINK32 -aa timer2.obj,,,,,timer2.res

Puc. 3.1.3. Результат работы программы на Рис. 3.1.2.

Обращаю Ваше внимание на весьма полезную функцию GetLocalTime. Информация, полученная с помощью этой функции (см. структуру DAT), легко может быть использована для самых разных целей, в том числе и для вывода на экран. Аналогично, с помощью функции SetLocalTime, Вы сможете установить текущее время. Для получения времени по Гринвичу используется функция GetSystemTime, которая, соответственно, с помощью SetSystemTime используется для установки времени в Гринвичском выражении. Аргументом во всех этих функциях является уже упомянутая выше структура (точнее указатель на нее).


39 Вообще говоря, три, так как само диалоговое окно также работает независимо.

III

В данном разделе мы рассмотрим весьма интересный вопрос о всплывающих подсказках. В визуальных языках программирования всплывающие подсказки организуются посредством установки соответствующих свойств объектов, расположенных на объекте-контейнере. Наша с вами задача разработать механизм, позволяющий без каких-либо дополнительных библиотек устанавливать подсказки на любые объекты, расположенные в окне. Итак, приступаем.

1. Прежде всего заметим, что всплывающая подсказка - это всего лишь окно с определенными свойствами. Вот эти свойства: DS_3DLOOK, WS_POPUP, WS_VISIBLE, WS_BORDER. В принципе можно экспериментировать - добавлять или удалять свойства. Но без одного свойства Вы никак не обойдетесь - это WS_POPUP. Собственно POPUP можно перевести как поплавок. Кроме того, определение всплывающего окна в файле ресурсов не должно содержать опции CAPTION.

2. Появление подсказки не должно менять ситуацию в диалоговом окне. Это значит - вызов подсказки должен быть немодальным, при помощи функции CreateDialogIndirect. Кроме того, следует предусмотреть переустановку фокуса на диалоговое окно. Для этого достаточно в нужном месте (см. Рис. 3.1.3) вызвать функцию SetFocus.

3. Итак, подсказка - это диалоговое окно, и, следовательно, оно должно иметь свою функцию. Что должна содержать эта функция? По крайней мере, обработку трех событий: WM_INITDIALOG, WM_PA1NT, WM_TIMER. По получении сообщения WM_INITDIALOG следует определить размер и положение подсказки. Кроме того, если мы предполагаем, что подсказка должна спустя некоторое время исчезать, следует установить таймер. По получении сообщения WM_PAINT следует вывести в окно подсказки текст. Если определять размер окна подсказки точно по строке выводимого текста, то цвет фона подсказки будет полностью определяться цветом выводимого текста. Наконец по приходе сообщения WM_TIMER мы закрываем подсказку.

4. С самой подсказкой более или менее ясно. Определимся теперь, как и где будет вызываться эта подсказка. Мне более импонирует такой подход: в основном диалоговом окне определяем таймер, в функции которого и будет проверяться положение курсора. В зависимости от этого положения и будет вызываться или удаляться подсказка. В функции таймера должно предусмотреть:

  1. Проверку положения курсора. Если курсор оказался на данном элементе, то вызывать подсказку. При этом желательно, чтобы подсказка появлялась бы с некоторой задержкой. Последнее можно обеспечить введением счетчика - вызывать подсказку, если счетчик превысил некоторое значение.
  2. Необходимо обеспечить удаление подсказки, если курсор покидает данный элемент.

На Рис. 3.1.3 представлена программа, которая демонстрирует описанный выше подход. На Рис. 3.1.4 представлено диалоговое окно с подсказками. В принципе, означенный подход не является единственным, и, разобравшись в данном подходе, вы сможете пофантазировать и придумать свои способы создания подсказок.

// файл HINT.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"
{
}
; файл HINT.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_COMMAND equ 111h
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 = IstrcpyA
DestroyWindow@4 = DestroyWindow
lstrlenA@4 = IstrlenA
GetDlgItem@8 = GetDlgItem
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=SendDlgItemMessageA
ExitProcess@4 = ExitProcess
GetModuleHandleA@4 = GetModuleHandleA
DialogBoxParamA@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
; структура размера окна
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
fIncUp DWORD 0
Reserv DB 32 dup (0)
PAINTSTR ENDS
; структура для получения позиции курсора
POINT STRUC
X DD ?
Y DD ?
POINT ENDS
; файл HINT.ASM
.386P
; плоская модель
.MODEL FLAT, stdcall
include hint.inc
; директивы компоновщику для подключения библиотек
IFDEF MASM
; для компоновщика LINK.EXE
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\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
;-----------------------------
; процедура окна
; расположение параметров в стеке
; [EBP+014Н] ; LPARAM
; [EBP+10Н] ; WAPARAM
; [EBP+0CH] ; MES
; [EBP+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
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
;---------------------------------------------
; процедура таймера
; расположение параметров в стеке
; [EBP+014Н] ; LPARAM - промежуток запуска Windows
; [EBP+10Н] ; WAPARAM - идентификатор таймера
; [EBP+0CH] ; WM_TIMER
; [EBP+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

Puc. 3.1.3. Пример диалогового окна с всплывающими подсказками.

Рис. 3.1.4. Диалоговое окно с всплывающими подсказками.

Комментарий к программе на Рис. 3.1.3.

1. Прежде всего обращу ваше внимание, что в данной программе мы используем условные конструкции времени выполнения. Данный шаг вполне закономерен и обусловлен только необходимостью несколько сократить объем, а также упростить читаемость программы. Вложенность условных конструкций, а также расстановка скобок вызваны желанием сократить длину строки и одновременно необходимостью транслировать программу как с помощью MASM32, так и с помощью TASM32. Я уже имел возможность сказать, что два ассемблера весьма сильно отличаются, когда речь идет о макросредствах.

2. Трансляция программы.
Пакет MASM32.

ML /c /coff /DMASM hint.asm
RC hint.rc
LINK /SUBSYSTEM:WINDOWS hint.obj hint.res
Пакет TASM32.
TASM32 /ml hint.asm
BRCC32 hint.rc
TLINK32 -aa hint.obj,,,,,hint.res

3. Как Вы, наверное, уже поняли, процедура таймера проверяет каждые 0.5 секунды положение курсора. Если курсор находится на элементе (окне редактирования или кнопке) и подсказка еще не вызвана (H1 или Н2 отлична от нуля), то вызывается подсказка. При этом учитывается еще величина счетчика (P1), чтобы подсказка появлялась с некоторой задержкой. Если при очередном вызове процедуры окажется, что курсор находится уже вне элемента, а подсказка еще на экране, то она удаляется. Данный механизм не учитывает тот случай, когда курсор быстро перейдет от одного элемента к другому. В этом случае вероятна ситуация, когда на экране окажется две подсказки. Впрочем, первая подсказка должна тут же исчезнуть.

4. В нашей программе на диалоговом окне расположено всего два элемента: окно редактирования и кнопка. Мы хотели показать, что в принципе не имеет значения, какой элемент: для любого из них может быть установлена подсказка. Положение подсказки по отношению к курсору легко регулируется, и Вы можете сами менять его.

5. Функция GetCursorPos получает положение курсора в абсолютных координатах относительно экрана. Здесь не возникает проблем, т.к. функция GetWindowRect также получает положение элемента окна в абсолютных координатах. Предварительно нам приходится определять дескриптор окна при помощи функции GetDlgItem.