На этом шаге мы рассмотрим создание и использование акселераторов.
На первый взгляд этот вопрос достаточно прост, но, как станет ясно, он тянет за собой множество других. Акселератор позволяет выбирать пункт меню просто сочетанием клавиш. Это очень удобно и быстро. Таблица акселераторов является ресурсом, имя которого должно совпадать с именем того меню (ресурса), пункты которого она определяет. Вот пример такой таблицы. Определяется один акселератор на пункт меню MENUP, имеющий идентификатор 4.
MENUP ACCELERATORS { VK_F5, 4, VIRTKEY }
А вот общий вид таблицы акселераторов:
Имя ACCELERATORS { Клавиша 1, Идентификатор пункта меню (1) [, тип] [, параметр] Клавиша 2, Идентификатор пункта меню (2) [, тип] [, параметр] Клавиша 3, Идентификатор пункта меню (3) [, тип] [, параметр] . . . . . Клавиша N, Идентификатор пункта меню (N) [, тип] [, параметр] }
Рассмотрим представленную схему. Клавиша - это либо символ в кавычках, либо ASCII-код символа, либо виртуальная клавиша. Если вначале стоит код символа, то тип задается как ASCII. Если используется виртуальная клавиша, то тип определяется как VIRTUAL. Все названия (макроимена) виртуальных клавиш можно найти в inciude-файлах (windows.h). Мы, как обычно, будем определять все макроимена непосредственно в программе.
Параметр может принимать одно из следующих значений:
Значение NOINVERT означает, что не подсвечивается выбранный при помощи акселератора пункт меню. Значения ALT, SHIFT, CONTROL означают, что, кроме клавиши, определенной в акселераторе, должна быть нажата одна из управляющих клавиш. Кроме этого, если клавиша определяется в кавычках, то нажатие при этом клавиши CONTROL определяется знаком ^: ^А.
А теперь поговорим о механизме работы акселераторов. Для того чтобы акселераторы работали, необходимо выполнить два условия:
Остановимся подробнее на втором пункте. Функция TranslateAccelerator преобразует сообщения WM_KEYDOWN и WM_SYSKEYDOWN в сообщения WM_COMMAND и WM_SYSCOMMAND соответственно. При этом в старшем слове параметра WPARAM помещается 1 как отличие для акселератора. В младшем слове, как вы помните, содержится идентификатор пункта меню. Возникает вопрос: для чего необходимы два сообщения WM_COMMAND И WM_SYSCOMMAND? Здесь все закономерно: сообщение WM_SYSCOMMAND генерируется для пункта системного меню или меню окна (рисунок 1).
Рис.1.Меню окна
Функция TranslateAccelerator возвращает ненулевое значение, если было произведено преобразование сообщения акселератора, в противном случае возвращается 0. Естественно включить вызов этой функции в цикл обработки сообщений. Вот этот фрагмент.
MSG_LOOP:
PUSH 0
PUSH 0
PUSH 0
PUSH OFFSET MSG
CALL GetMessageA@16
CMP EAX, 0
JE END_LOOP
;Транслировать сообщение акселератора.
PUSH OFFSET MSG
PUSH [ACC]
PUSH [NEWHWND]
CALL TranslateAcceleratorA@12
CMP EAX, 0
JNE MSG_LOOP
PUSH OFFSET MSG
CALL TranslateMessage@4
PUSH OFFSET MSG
CALL DispatchMessageA@4
JMP MSG_LOOP
END_LOOP:
Фрагмент вам знаком, но в него вставлена функция TranslateAccelerator. Первым параметром этой функции идет дескриптор приложения, вторым параметром идет дескриптор таблицы акселераторов ([АСС]), получаемый при загрузке таблицы с помощью функции LoadAccelerators. Третий параметр - адрес, где содержится сообщение, полученное функцией GetMessage.
И вот тут начинается самое интересное, потому что вы можете сказать, что кольцо обработки сообщений используется, когда основное окно создается обычным способом, посредством регистрации класса окна и последующего вызова функции CreateWindow. В этих шагах мы рассматриваем диалоговые окна. Конечно, диалоговые окна могут порождаться обычным окном, и здесь все нормально. Но как быть в простейшем случае одного диалогового окна? Первое, что приходит в голову, - это поместить функцию TranslateAcceierator в функцию окна и там, на месте, осуществлять преобразования. Но сообщения от акселератора до этой функции не доходят. И здесь мы подходим к новому материалу: модальные и немодальные окна.
До сих пор мы работали с модальными диалоговыми окнами. Эти окна таковы, что при их вызове программа должна дожидаться, когда окно будет закрыто. Существуют и немодальные диалоговые окна. После их вызова программа продолжает свою работу. При этом немодальное окно позволяет переключаться на другие окна. Каковы особенности создания немодального диалога?
Ниже представлена программа, демонстрирующая немодальный диалог с меню и обработкой сообщений акселератора.
//Файл menu1.rc. //Определение констант. #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L #define WS_POPUP 0x80000000L #define VK_F5 0x74 #define st WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX MENUP MENU { POPUP "&Первый пункт" { MENUITEM "&Первый", 1 MENUITEM "В&торой", 2, HELP } POPUP "&Второй пункт" { MENUITEM "Трети&й", 3 MENUITEM "Четверт&ый", 4 MENUITEM SEPARATOR POPUP "Еще подмен&ю" { MENUITEM "Десятый пунк&т", 6 } } MENUITEM "Вы&ход", 5 } //Идентификаторы. #define IDI_ICON1 100 //Определили пиктограмму. IDI_ICON1 ICON "help.ico" //Определение диалогового окна. DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_POPUP | st CAPTION "Пример немодального диалогового окна" FONT 8, "Arial" { } MENUP ACCELERATORS { VK_F5, 4, VIRTKEY, ALT }
;Константы. ;Сообщение приходит при закрытии окна. WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SETICON equ 80h WM_COMMAND equ 111h ;Прототипы внешних процедур. EXTERN ShowWindow@8:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadIconA@8:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SetMenu@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN LoadAcceleratorsA@8:NEAR EXTERN TranslateAcceleratorA@12:NEAR EXTERN GetMessageA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN CreateDialogParamA@20:NEAR EXTERN DestroyWindow@4:NEAR EXTERN TranslateMessage@4:NEAR ;Структуры ;Структура сообщения. MSGSTRUCT STRUC MSHWND DD ? ;Идентификатор окна, получающего сообщение. MSMESSAGE DD ? ;Идентификатор сообщения. MSWPARAM DD ? ;Доп. информация о сообщении. MSLPARAM DD ? ;Доп. информация о сообщении. MSTIME DD ? ;Время посылки сообщения. MSPT DD ? ;Положение курсора во время посылки сообщения. MSGSTRUCT ENDS
.386P ;Плоская модель. .MODEL FLAT, STDCALL include pr39_1.asm ;Директивы компоновщику для подключения библиотек. includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> HINST DD 0 ;Здесь хранится дескриптор приложения. PA DB 'DIAL1',0 PMENU DB "MENUP",0 STR1 DB "Выход из программы",0 STR2 DB "Сообщение", 0 STR3 DB "Выбран четвертый",0 ACC DWORD ? _DATA ENDS ;Сегмент кода. _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ;Получить дескриптор приложения. PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;Загрузить акселераторы. PUSH OFFSET PMENU PUSH [HINST] CALL LoadAcceleratorsA@8 MOV ACC,EAX ;Запомнить дескриптор таблицы. ;Создать немодальный диалог. PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL CreateDialogParamA@20 ;Визуализировать немодальный диалог. MOV NEWHWND,EAX PUSH 1 ;SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ;Показать созданное окно. ;Кольцо обработки сообщений. MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP EAX, 0 JE END_LOOP ;Транслировать сообщение акселератора. PUSH OFFSET MSG PUSH [ACC] PUSH [NEWHWND] CALL TranslateAcceleratorA@12 CMP EAX, 0 JNE MSG_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: 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 ;Закрыть диалоговое окно. JMP L5 L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L3 ;Загрузить пиктограмму. PUSH 100 ;Идентификатор пиктограммы. PUSH [HINST] ;Идентификатор процесса. CALL LoadIconA@8 ;Установить пиктограмму. PUSH EAX PUSH 0 ;Ттип пиктограммы (маленькая). PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 ;Загрузить меню. PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ;Установить меню. PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 ;------------------------ MOV EAX,1 ;Возвратить ненулевое значение. JMP FIN ;Проверяем, не случилось ли чего с управляющими ;элементами на диалоговом окне. L3: CMP DWORD PTR [EBP+0CH],WM_COMMAND JE L6 JMP FINISH ;Здесь определяем идентификатор, в данном случае ;это идентификатор пункта меню сообщение. L6: CMP WORD PTR [EBP+10H],4 JNE L4 PUSH 0 ;MB_OK PUSH OFFSET STR2 PUSH OFFSET STR3 PUSH 0 CALL MessageBoxA@16 JMP FINISH L4: CMP WORD PTR [EBP+10H],5 JNE FINISH ;Ссообщение. PUSH 0 ;MB_OK PUSH OFFSET STR2 PUSH OFFSET STR1 PUSH 0 CALL MessageBoxA@16 ;Закрыть диалоговое немодальное окно. L5: PUSH DWORD PTR [EBP+08H] CALL DestroyWindow@4 ;Послать сообщение для выхода из кольца ;обработки сообщений. PUSH 0 CALL PostQuitMessage@4 ;Сообщение WM_QUIT. FINISH: MOV EAX,0 FIN: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Результат работы программы представлен на рисунке 1:
Рис.2. Результат работы приложения при нажатой комбинации клавиш ALT+F5
Несколько комментариев по поводу приведенной программы.
Немодальный диалог задается в файле ресурсов так же, как и модальный. Поскольку в свойствах окна нами не было указано свойство WS_VISIBLE, в программе, для того чтобы окно было видимым, мы используем функцию ShowWindow.
Выход из программы в нашем случае предполагает не только удаление из памяти диалогового окна, что достигается посредством функции DestroyWindow, но и выход из цикла обработки сообщений. Последнее осуществляется через вызов функции PostQuitMessage.
В заключение отметим, что остались неосвещенными также следующие виды ресурсов:
имя RCDATA BEGIN raw-data . . . . END
ID VERSIONINFO BEGIN block-statement . . . . END
Оба этих ресурса используются значительно реже остальных, и мы на них останавливаться не будем.
На следующем шаге мы рассмотрим особенности работы с ресурсами в TASM32.