Шаг 42.
Примеры программ с ресурсами. Горячие клавиши

    На этом шаге мы рассмотрим использование горячих клавиш.

    Итак, продолжим рассматривать ресурсы. Хочется рассказать о весьма интересном приеме, который можно использовать при работе с окнами редактирования. В визуальных языках типа 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.
//Файл 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
}


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


    Основной файл приложения, содержит подключение файла pr42_1.asm (его имя pr42_2.asm).
.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 имеет следующие параметры:

    В нашем случае виртуальный код клавиши и идентификатор горячей клавиши совпадают. Конечно, здесь есть поле для дальнейшего усовершенствования. Скажем, исключить из обработки клавиши управления курсором. Надеемся, читатель справится с этим самостоятельно.

    На следующем шаге мы рассмотрим управление списками.




Предыдущий шаг Содержание Следующий шаг