Шаг 39.
Язык описания ресурсов. Акселераторы

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

    На первый взгляд этот вопрос достаточно прост, но, как станет ясно, он тянет за собой множество других. Акселератор позволяет выбирать пункт меню просто сочетанием клавиш. Это очень удобно и быстро. Таблица акселераторов является ресурсом, имя которого должно совпадать с именем того меню (ресурса), пункты которого она определяет. Вот пример такой таблицы. Определяется один акселератор на пункт меню 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 в функцию окна и там, на месте, осуществлять преобразования. Но сообщения от акселератора до этой функции не доходят. И здесь мы подходим к новому материалу: модальные и немодальные окна.

    До сих пор мы работали с модальными диалоговыми окнами. Эти окна таковы, что при их вызове программа должна дожидаться, когда окно будет закрыто. Существуют и немодальные диалоговые окна. После их вызова программа продолжает свою работу. При этом немодальное окно позволяет переключаться на другие окна. Каковы особенности создания немодального диалога?

  1. Немодальный диалог создается при помощи функции CreateDialog.
  2. Уничтожается немодальный диалог функцией DestroyWindow.
  3. Для того чтобы немодальный диалог появился на экране, нужно либо указать у него свойство WS_VISIBLE, либо после создания диалога выполнить команду ShowWindow.

    Ниже представлена программа, демонстрирующая немодальный диалог с меню и обработкой сообщений акселератора.


    Ресурсный файл menu1.rc.
//Файл 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
}


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


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

    В заключение отметим, что остались неосвещенными также следующие виды ресурсов:

    Оба этих ресурса используются значительно реже остальных, и мы на них останавливаться не будем.

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




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