Ассемблер для Windows

Итак продолжим рассматривать ресурсы



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


      Содержание раздела