На этом шаге мы рассмотрим использование горячих клавиш.
Итак, продолжим рассматривать ресурсы. Хочется рассказать о весьма интересном приеме, который можно использовать при работе с окнами редактирования. В визуальных языках типа Visual Basic, Delphi окна редактирования можно так запрограммировать, а точнее, задать их свойства, что они позволят вводить только вполне определенные символы. В Delphi это свойство называется EditMask. Рассмотрим, как подобное реализовать только API-средствами. Но обо всем по порядку.
Обычное окно при нажатии клавиши (если в нем находится фокус) получает сообщения WM_KEYDOWN, WM_KEYUP и их квинтэссенцию WM_CHAR. Но в данном случае мы имеем дело не с обычным окном, а с диалоговым. Диалоговое окно таких сообщений не получает. Остается надеяться на сообщения, посылаемые на события, происходящие с самим элементом "окном редактирования". Но, увы, и здесь нас ждут разочарования. Данный элемент получает лишь два сообщения из тех, которые нас хоть как-то могут заинтересовать. Это сообщение EN_UPDATE и сообщение EN_CHANGE. Оба сообщения приходят, когда уже произведено изменение в окне редактирования. Но сообщение EN_UPDATE приходит, когда изменения на экране еще не произведены, a EN_CHANGE - после таких изменений. Нам придется сначала получить содержимое окна редактирования, определить, какой символ туда поступил последним, и если он недопустим, удалить его из строки и послать строку в окно снова. Добавьте сюда еще проблему, связанную с положением курсора и вторичным приходом сообщения EN_UPDATE. Достаточно длинный путь.
Есть другой более изящный и короткий путь: использовать понятие горячей клавиши (HOTKEY). Мы ограничимся лишь программными свойствами горячих клавиш, т.е. свойствами, которые необходимо знать программисту, чтобы использовать горячие клавиши в своих программах.
Горячая клавиша может быть определена для любой виртуальной клавиши, клавиши, определяемой через макроконстанты с префиксом VK. Для обычных алфавитно-цифровых клавиш значение этих констант просто совпадает с кодами ASCII. Возможны также сочетания с управляющими клавишами Alt, Ctrl, 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 //Текст, идентификатор З. 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_VISIBLE | WS_TABSTOP, 180, 76, 50, 14 }
;Константы. ;Сообщение приходит при закрытии окна. 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 DD ? ;Идентификатор окна, получающего сообщение. MSMESSAGE DD ? ;Идентификатор сообщения. MSWPARAM DD ? ;Доп. информация о сообщении. MSLPARAM DD ? ;Доп. информация о сообщении. MSTIME DD ? ;Время посылки сообщения. MSPT DD ? ;Положение курсора во время посылки сообщения. MSGSTRUCT ENDS
.386P ;Плоская модель. .MODEL FLAT, STDCALL include pr42_1.asm ;Директивы компоновщику для подключения библиотек. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' 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 ;------------------------------------- ;------------------------------------- ;Процедура окна. ;Расположение параметров в стеке: ;[ЕВР+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 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 [TAB+EBX] PUSH EAX PUSH DWORD PTR [EBP+08H] 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 [TAB+EBX] PUSH EAX PUSH 0 PUSH EAX PUSH DWORD PTR [EBP+08H] 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+08H] ;Дескриптор окна. CALL MessageBoxA@16 FINISH: MOV EAX,0 FIN: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Результат работы приложения изображен на рисунке 1:
Рис.1. Результат работы при нажатии нецифровой клавиши в первой строке редактирования
Прокомментируем приведенную программу.
Самое главное - разберитесь с тем, как програма обнаруживает получие и потерю фокуса первым окном. Сначала выясняется, что сообщение пришло от окна редактирования с идентификатором 1, а затем - какое сообытие пришло: EN_SETFOCUS или EN_KILLFOCUS. В первом случае мы включаем горячие клавиши, а во втором - выключаем их.
В области данных задаем таблицу горячих клавиш. Напомним, что функция RegisterHotKey имеет следующие параметры:
В нашем случае виртуальный код клавиши и идентификатор горячей клавиши совпадают. Конечно, здесь есть поле для дальнейшего усовершенствования. Скажем, исключить из обработки клавиши управления курсором. Надеемся, читатель справится с этим самостоятельно.
На следующем шаге мы рассмотрим управление списками.