Вторник, 15.07.2025, 13:59 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 4. Примеры программ использующих ресурсы(2)

Программа на Рис. 2.4.1 имеет ряд механизмов, к обсуждению которых я намерен сейчас приступить. Для начала замечу, что в программе используются три ресурса: два меню и таблица акселераторов (см. файл ресурсов).

1. Первое, на что хочу обратить Ваше внимание, - это переменная PRIZN. В ней хранится состояние меню: 2 - загружено меню MENUP, 1 - меню отсутствует, 0 - загружено меню MENUC. Начальное состояние обеспечивается заданием меню при регистрации класса окна:

MOV DWORD PTR [WC.CLMENNAME], OFFSET MEN

2. Второе - это кнопка. Механизм распознавания нажатия кнопки мы уже разбирали, так что больше на этом останавливаться не будем. Одно из событий, которое может произойти при нажатии кнопки - это удаление меню. Удаляется меню при помощи функции DestroyMenu. После удаления необходимо обновить содержимое окна. Это достигается последовательностью двух команд ShowWindow.

3. Еще одно событие, которое происходит при нажатии кнопки, это смена меню. Интересно, что смена меню происходит автоматически, если мы загрузим и установим новое меню.

4. Выбор одного из пунктов меню MENUP также приводит к смене меню. Здесь должно быть все понятно, поскольку обращение идет к тому же участку программы, что и в случае нажатия кнопки.

5. Интересная ситуация возникает с акселератором. Акселераторная клавиша у нас F5. При ее нажатии генерируется такое же сообщение, как при выборе пункта "Четвертый" меню MENUP. Важно то, что такое же сообщение будет генерироваться и тогда, когда загружается меню MENUC и когда меню не будет. А поскольку наша процедура обрабатывает сообщение в любом случае, клавиша F5 будет срабатывать всегда.

6. Рассмотрим теперь то, как производится определение названия выбранного пункта меню. Центральную роль в этом механизме играет сообщение WM_MENUSELECT. Это сообщение приходит всегда, когда выбирается пункт меню. Тут важно отметить, что когда мы активизируем меню, то в начале приходит сообщение WM_MENUSELECT со значением LPARAM, которое определяет идентификатор меню равным нулю. Этим целям служат строки:

CMP WORD PTR [EBP+14H], 0
JE FINISH

7. По получении сообщения WM_MENUSELECT в младшем слове параметра WPARAM может содержаться либо идентификатор пункта меню, либо номер заголовка выпадающего меню. Это ключевой момент. Нам важно это знать, так как строка заголовка выпадающего меню и строка пункта меню получаются по-разному. Определить, что выбрано, можно по старшему слову WPARAM. Мы используем для этого константу MF_POPUP: TEST WORD PTR [EBP+12H], MF_POPUP. Обратите внимание, как удобна и как кстати здесь команда SETNE.

8. Далее, для получения строки-названия используется функция GetMenuItemInfo. Третьим параметром этой функции как раз и может быть либо ноль, либо единица. Если ноль, то второй параметр - это идентификатор пункта меню, если единица, то второй параметр - номер заголовка выпадающего меню. Четвертым параметром является указатель на структуру, которая и будет заполняться в результате выполнения функции. Некоторые поля этой структуры должны быть, однако, заполнены заранее. Обращаю внимание на поле dwTypeData, которое должно содержать указатель на буфер, получающий необходимую нам строку. При этом поле cch должно содержать длину этого буфера. Но для того чтобы поля dwTypeData и cch трактовались функцией именно как указатель на буфер и его длину, поля fMask и fType должны быть правильно заполнены (см. программу). Наконец, поле cbSize должно содержать длину всей структуры.

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

II

Итак, продолжим рассматривать ресурсы. Хочется рассказать о весьма интересном приеме, который можно использовать при работе с окнами редактирования. Наверное, Вы работали с визуальными языками типа Visual Basic, Delphi и пр. и обратили внимание, что окна редактирования можно так запрограммировать, а точнее, задать их свойства, что они позволят вводить только вполне определенные символы. В Delphi это свойство называется EditMask. Я думаю. Вам хотелось бы понять, как подобное реализовать только API-средствами. Но обо всем по порядку.

Обычное окно при нажатии клавиши (если в нем находится фокус) получает сообщения WM_KEYDOWN, WM_KEYUP и их квинтэссенцию WM_CHAR. Но в данном случае мы имеем дело не с обычным окном, а с диалоговым. Диалоговое окно таких сообщений не получает. Остается надеяться на сообщения, посылаемые на события, происходящие с самим элементом "окном редактирования". Но, увы, и здесь нас ждут разочарования. Данный элемент получает лишь два сообщения из тех, которые нас хоть как-то могут заинтересовать. Это сообщение EN_UPDATE и сообщение EN_CHANGE. Оба сообщения приходят, когда уже произведено изменение в окне редактирования. Но сообщение EN_UPDATE приходит, когда изменения на экране еще не произведены, а EN_CHANGE - после таких изменений. Нам придется сначала получить содержимое окна редактирования, определить, какой символ туда поступил последним, и если он недопустим, удалить его из строки и послать строку в окно снова. Добавьте сюда еще проблему, связанную с положением курсора и вторичным приходом сообщения EN_UPDATE. Лично я по такому пути бы не пошел.

Есть другой более изящный и короткий путь: использовать понятие горячей клавиши (HOTKEY). Мы ограничимся лишь программными свойствами горячих клавиш, то есть свойствами, которые необходимо знать программисту, чтобы использовать горячие клавиши в своих программах.

Горячая клавиша может быть определена для любой виртуальной клавиши, клавиши, определяемой через макроконстанты с префиксом VK. Для обычных алфавитно-цифровых клавиш значение этих констант просто совпадает с кодами ASCII. Возможны также сочетания с управляющими клавишами Alt, Control, Shift. После того как для данного окна определена горячая клавиша, при ее нажатии на функцию окна приходит сообщение WM_HOTKEY. По параметрам можно определить, какая именно горячая клавиша была нажата. Существенно, что понятие горячей клавиши глобально, т.е. она будет срабатывать, если будут активны другие окна и даже окна других приложений. Это требует от программиста весьма аккуратной работы, так как вы можете заблокировать нормальную работу других приложений. Т.е. необходимо отслеживать, когда данное окно активно, а когда нет. Этому весьма могут помочь сообщения WM_ACTIVATE и WM_ACTIVATEAPP. Первое сообщение всегда приходит на функцию окна тогда, когда окно активизируется или деактивизируется. Первый раз сообщение приходит при создании окна. Вот при получении этого сообщения и есть резон зарегистрировать горячие клавиши. Второе сообщение всегда приходит на функцию окна, когда окно теряет "фокус" - активизируется другое окно. Соответственно, при получении этого сообщения и следует отменить регистрацию этих клавиш.

Для работы с горячими клавишами используют в основном две функции: RegisterHotKey и UnregisterHotKey. Функция RegisterHotKey имеет следующие параметры:

  • первый - дескриптор окна;
  • второй - идентификатор горячей клавиши;
  • третий - модификатор, определяющий, не нажата ли управляющая клавиша;
  • четвертый - виртуальный код клавиши.

Функция UnregisterHotKey имеет всего два параметра:

  • первый - дескриптор окна;
  • второй - идентификатор.

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

Рассмотрим простой пример диалогового окна, на котором расположены два окна редактирования и кнопка выхода. Поставим перед собой такую цель. Первое окно редактирования должно пропускать только цифры от 0 до 9. Во второе окно можно вводить все символы. Выше рассматривался возможный механизм использования горячих клавиш с сообщениями WM_ACTIVATE и WM_ ACTIVATEAPP. Ясно, что эти события в данном случае нам ничем не помогут. Здесь дело тоньше, надо использовать сообщения, относящиеся к одному окну редактирования. Это сообщения EN_SETFOCUS и EN_KILLFOCUS, передаваемые, естественно, через сообщение WM_COMMAND. Ниже представлена программа, демонстрирующая этот механизм, и комментарий к ней. Сообщение EN_SETFOCUS говорит о том, что окно редактирования приобрело фокус (стало активным), а сообщение EN_KILLFOCUS - что окно редактирования потеряло фокус.

// файл dial1.rc
// определение констант
// стили окна
#define WS_SYSMENU 0x00080000L
#define WS_MINIMIZEBOX 0x00020000L
#define WS_MAXIMIZEBOX 0x00010000L

// текст в окне редактирования прижат к левому краю
#define ES_LEFT 0x0000L
// стиль всех элементов на окне
#define WS_CHILD 0x40000000L
// элементы на окне должны быть изначально видимы
#define WS_VISIBLE 0x10000000L
// бордюр вокруг элемента
#define WS_BORDER 0x00800000L
// при помощи TAB можно по очереди активизировать элементы
#define WS_TABSTOP 0x00010000L
// прижать строку к левому краю отведенного поля
#define SS_LEFT 0x00000000L
// стиль кнопка
#define BS_PUSHBUTTON 0x00000000L
// центрировать текст на кнопке
#define BS_CENTER 0x00000300L
#define DS_LOCALEDIT 0x20L

// определение диалогового окна
DIAL1 DIALOG 0, 0, 240, 120
STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
CAPTION "Пример диалогового окна"
FONT 8, "Arial"
{
// поле редактирования, идентификатор 1
CONTROL "", 1, "edit", ES_LEFT | WS_CHILD
 | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 24, 20, 128, 12
// еще одно поле редактирования, идентификатор 2
CONTROL "", 2, "edit", ES_LEFT | WS_CHILD
 | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 24, 52, 127, 12
// текст, идентификатор 3
CONTROL "Строка 1", 3, "static", SS_LEFT 
 | WS_CHILD | WS_VISIBLE, 164, 22, 60, 8
// еще текст, идентификатор 4
CONTROL "Строка 2", 4, "static", SS_LEFT
 | WS_CHILD | WS_VISIBLE, 163, 54, 60, 8
// кнопка, идентификатор 5
CONTROL "Выход", 5, "button", BS_PUSHBUTTON
 | BS_CENTER | WS_CHILD | WS_VISlBLE | WS_TABSTOP,
 180, 76, 50, 14
}


;файл dial1.inc
; константы
; сообщение приходит при закрытии окна
WM_CLOSE equ 10h
WM_INITDIALOG equ 110h
WM_COMMAND equ 111h
WM_SETTEXT equ 0Ch
WM_HOTKEY equ 312h
EN_SETFOCUS equ 100h
EN_KILLFOCUS equ 200h

; прототипы внешних процедур
EXTERN UnregisterHotKey@8:NEAR
EXTERN RegisterHotKey@16: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 GetDlgItem@8:NEAR
EXTERN MessageBoxA@16:NEAR

; структуры
; структура сообщения
MSGSTRUCT STRUC
 MSHWND DWORD ?
 MSMESSAGE DWORD ?
 MSWPARAM DWORD ?
 MSLPARAM DWORD ?
 MSTIME DWORD ?
 MSPT DWORD ?
MSGSTRUCT ENDS

;файл dial.asm
.386P
; плоская модель
.MODEL FLAT, stdcall
include dial1.inc
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
; ------------------------------------------------------------
; сегмент данных DATA SEGMENT DWORD PUBLIC USE32 'DATA'
 MSG MSGSTRUCT <?>
 HINST DD 0 ; дескриптор приложения
 PA DB "DIAL1",0
 STR1 DB "Неправильный символ !",0
 STR2 DB "Ошибка !",0
; таблица для создания горячих клавиш
TAB DB 32,33,34,35,36,37,38,39,40
 DB 41,42,43,44,45,46,47,58,59,60
 DB 61,62,63,64,65,66,67,68,69,70
 DB 71,72,73,74,75,76,77,78,79,80
 DB 81,82,83,84,85,86,87,88,89,90
 DB 91,92,93,94,95,96,97,98,99,100
 DB 101,102,103,104,105,106,107,108,109,110
 DB 111,112,113,114,115,116,117,118,119,120
 DB 121,122,123,124,125,126,127,128,129,130
 DB 131,132,133,134,135,136,137,138,139,140
 DB 141,142,143,144,145,146,147,148,149,150
 DB 151,152,153,154,155,156,157,158,159,160
 DB 161,162,163,164,165,166,167,168,169,170
 DB 171,172,173,174,175,176,177,178,179,180
 DB 181,182,183,184,185,186,187,188,189,190
 DB 191,192,193,194,195,196,197,198,199,200
 DB 201,202,203,204,205,206,207,208,209,210
 DB 211,212,213,214,215,216,217,218,219,220
 DB 221,222,223,224,225,226,227,228,229,230
 DB 231,232,233,234,235,236,237,238,239,240
 DB 241,242,243,244,245,246,247,248,249,250
 DB 251,252,253,254,255
_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+10H] ; 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
 PUSH 0
 PUSH DWORD PTR [EBP+08H]
 CALL EndDialog@8
 MOV EAX, 1
 JMP FIN
L1:
 CMP DWORD PTR [EBP+0CH],WM_INITDIALOG
 JNE L2
; здесь заполнить окна редактирования, если надо
;
;
 MOV EAX, 1
 JMP FIN
L2:
 CMP DWORD PTR [EBP+0CH],WM_COMMAND
 JNE L5
; кнопка выхода ?
 CMP WORD PTR [EBP+10H], 5
 JNE L3
 PUSH 0
 PUSH DWORD PTR [EBP+08H]
 CALL EndDialog@8
 MOV EAX, 1
 JMP FIN
L3:
 CMP WORD PTR [EBP+10H], 1
 JNE FINISH
; блок обработки сообщений первого окна редактирования
 CMP WORD PTR [EBP+12H], EN_KILLFOCUS
 JNE L4
; окно редактирования с идентификатором 1 теряет фокус
 MOV EBX, 0
; снимаем все горячие клавиши
L33:
 MOVZX EAX,BYTE PTR [ТАВ+EBX]
 PUSH EAX
 PUSH DWORD PTR [EBP+08Н]
 CALL UnregisterHotKey@8
 INC EBX
 CMP EBX, 214
 JNE L33
 MOV EAX, 1
 JMP FIN
L4:
 CMP WORD PTR [EBP+12H],EN_SETFOCUS
 JNE FINISH
; окно редактирования с идентификатором 1 получает фокус
 MOV EBX, 0
; устанавливаем горячие клавиши
L44:
 MOVZX EAX,BYTE PTR [ТАВ+EBX]
 PUSH EAX
 PUSH 0
 PUSH EAX
 PUSH DWORD PTR [EBP+08Н]
 CALL RegisterHotKey@16
 INC EBX
 CMP EBX, 214
 JNE L44
 MOV EAX, 1
 JMP FIN
L5:
 CMP DWORD PTR [EBP+0CH],WM_HOTKEY
 JNE FINISH
; здесь реакция на неправильно введенный символ
 PUSH 0 ; МВ_ОК
 PUSH OFFSET STR2
 PUSH OFFSET STR1
 PUSH DWORD PTR [EBP+08Н] ; дескриптор окна
 CALL MessageBoxA@16
FINISH:
 MOV EAX, 0
FIN:
 POP EDI
 POP ESI
 POP EBX
 POP EBP
 RET 16
WNDPROC ENDP
_TEXT ENDS
END START

Рис. 2.4.2. Пример использования горячих клавиш с диалоговым окном.


Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0