Сообщение WM_INITMENUPOPUP

Это сообщение поступает перед отображением временного меню. Параметр wParam равен дескриптору меню. Младшее слово параметра lParam равно позиции этого меню в меню верхнего уровня, старшее слово lParam равно 1 для системного меню и 0 – для обычного. Если это сообщение обрабатывают, то возвращают 0. Обработка обычно сводится к изменению состояния элементов меню.

Сообщение WM_COMMAND

Это сообщение поступает после выбора строки меню. Младшее слово параметра wParam равно идентификатору выбранной команды, а старшее слово равно 0. После обработки сообщения возвращают 0.

Сообщение WM_MENUSELECT

Сообщение WM_MENUSELECT поступает в процессе перемещения курсора меню по строкам меню. Младшее слово параметра wParam равно идентификатору команды или позиции строки (если при выборе строки отображается временное меню), а старшее слово содержит флажки состояния элементов меню из следующей таблицы:

Значение Состояние элемента
MF_CHECKED Отмечен
MF_DISABLED Заблокирован
MF_GRAYED Недоступен
MF_HILITE Высвечен
MF_MOUSESELECT Выбран мышью
MF_POPUP Открывает временное меню
MF_SYSMENU Принадлежит системному меню окна. Параметр lParam содержит дескриптор этого меню

 

Если старшее слово wParam содержит 0xFFFF и lParam = NULL, то Windows закрыл меню. Параметр lParam содержит дескриптор меню, по которому перемещается курсор.

Если это сообщение обрабатывают, то возвращают 0.

Плавающее меню

Плавающее меню создают обычным способом, но не вставляют в другое меню. Для отображения и выбора строк этого меню вызывают функцию TrackPopupMenu:

 

BOOL TrackPopupMenu( HMENU hMenu, UINT uFlags, int x, int y,

int nReserved, HWND hwnd, CONST RECT *prcRect);

 

Эта функция выводит на экран плавающее меню и создает свой собственный цикл обработки сообщений, завершающий работу после выбора строки. Она не возвращает управление до тех пор, пока работа с меню не будет завершена выбором строки или отказом от выбора.

Параметры:

1. hMenu – дескриптор отображаемого плавающего меню. Он может быть создан функцией CreatePopupMenu или получен с помощью функции GetSubMenu.

2. uFlags – комбинация флажков, которые задают функциональные параметры плавающего меню.

2.1. Следующие константы задают способ размещения меню по горизонтали относительно параметра х:

Константа Пояснение
ТРМ_CENTERALIGN Центр меню по горизонтали совпадает с х
ТРМ_LEFTALIGN Левый край меню совпадает с х
TPM_RIGHTALIGN Правый край меню совпадает с х

2.2. Следующие константы задают способ размещения меню по вертикали относительно параметра у:

Константа Пояснение
ТРМ_BOTTOMALIGN Нижний край меню совпадает с у
TPM_TOPALIGN Верхний край меню совпадает с у
TPM_VCENTERALIGN Центр меню по вертикали совпадает с у

2.3. Следующие константы задают способ выбора строк меню без указания окна-владельца для меню:

Константа Пояснение
ТРМ_NONOTIFY Не посылать сообщения о выборе строки
TPM_RETURNCMD Возвращать идентификатор выбранной команды

2.4. Следующие константы задают кнопку мыши, которую прослеживает плавающее меню:

Константа Пояснение
TPM_LEFTBUTTON Прослеживает левую кнопку мыши
TPM_RIGHTBUTTON Прослеживает правую кнопку мыши

3. х – координата по горизонтали от левого края экрана.

4. у – координата по вертикали от верхнего края экрана.

5. nReserved – зарезервированный параметр, должен быть всегда равен нулю.

6. hwnd – дескриптор уже существующего окна-владельца, которое получит сообщения от меню. Сообщение WM_COMMAND окно получит только после завершения работы функции TrackPopupMenu.

7. prcRect – указатель на прямоугольную область, находясь в пределах которой можно работать с меню. Если сделать щелчок мышью за пределами этого прямоугольника, плавающее меню исчезнет. Если prcRect = NULL, то эта область ограничена прямоугольной рамкой плавающего меню.

В случае успешного выполнения функция возвращает ненулевое значение. Если в параметре uFlags задано TPM_RETURNCMD, то возвращаемое значение равно идентификатору команды выбранной строки. Если элемент не выбран, возвращаемое значение – нуль.

Задача. После нажатия правой клавиши мыши отобразить плавающее меню.

Листинг 4.5. Плавающее меню

#include <windows.h>

#include <tchar.h>

 

#define CM_EDIT_CUT 2003

#define CM_EDIT_COPY 2004

#define CM_EDIT_PASTE 2005

 

BOOL RegClass(WNDPROC, LPCTSTR, UINT);

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

 

HINSTANCE hInstance;

TCHAR szClass[] = TEXT("FloatMenu");

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

MSG msg;

HWND hwnd;

hInstance = hInst;

if (!RegClass(WndProc, szClass, COLOR_WINDOW)) return FALSE;

hwnd = CreateWindow(szClass, TEXT("Окно с плавающим меню"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

if (!hwnd) return FALSE;

while(GetMessage(&msg, 0, 0, 0))

DispatchMessage(&msg);

return msg.wParam;

}

 

BOOL RegClass(WNDPROC Proc, LPCTSTR szName, UINT brBackground)

{

WNDCLASS wc;

wc.style = wc.cbClsExtra = wc.cbWndExtra = 0;

wc.lpfnWndProc = Proc;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(brBackground +1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szName;

return (RegisterClass(&wc) != 0);

}

 

BOOL CreateMenuItem(HMENU hMenu, TCHAR *str, UINT uIns, UINT uCom, HMENU hSubMenu, BOOL flag, UINT fType)

{

MENUITEMINFO mii;

mii.cbSize = sizeof(MENUITEMINFO);

mii.fMask = MIIM_STATE | MIIM_TYPE | MIIM_SUBMENU | MIIM_ID;

mii.fType = fType;

mii.fState= MFS_ENABLED;

mii.dwTypeData = str;

mii.cch = sizeof(str);

mii.wID = uCom;

mii.hSubMenu = hSubMenu;

return InsertMenuItem(hMenu, uIns, flag, &mii);

}

 

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch (msg)

{

case WM_COMMAND:

{

switch (LOWORD(wParam))

{

case CM_EDIT_CUT:

{ MessageBox(hwnd, TEXT("Вырезать"), TEXT("Сообщение"), MB_OK); return 0; }

case CM_EDIT_COPY:

{ MessageBox(hwnd, TEXT("Копировать"), TEXT("Сообщение"), MB_OK); return 0; }

case CM_EDIT_PASTE:

{ MessageBox(hwnd, TEXT("Вклеить"), TEXT("Сообщение"), MB_OK); return 0; }

}

return 0;

}

case WM_RBUTTONDOWN:

{

//Берем экранные координаты курсора мыши

DWORD xyPos = GetMessagePos();

WORD xPos = LOWORD(xyPos),

yPos = HIWORD(xyPos);

HMENU hFloatMenu = CreatePopupMenu();

int i=0;

CreateMenuItem( hFloatMenu, TEXT("Вырезать"), i++, CM_EDIT_CUT, NULL, FALSE, MFT_STRING);

CreateMenuItem( hFloatMenu, TEXT("Копировать"), i++, CM_EDIT_COPY, NULL, FALSE, MFT_STRING);

CreateMenuItem( hFloatMenu, TEXT("Вклеить"), i++, CM_EDIT_PASTE, NULL, FALSE, MFT_STRING);

//Выводим меню в позиции курсора мыши

TrackPopupMenu( hFloatMenu, TPM_CENTERALIGN | TPM_LEFTBUTTON | TPM_VCENTERALIGN, xPos, yPos, 0, hwnd, NULL);

DestroyMenu( hFloatMenu);

return 0;

}

case WM_DESTROY: { PostQuitMessage(0); return 0; }

}

return DefWindowProc(hwnd, msg, wParam, lParam);

}

 

При нажатии правой клавиши мыши в функцию окна поступает сообщение WM_RBUTTONDOWN. При этом параметр lParam содержит координаты курсора мыши относительно левого верхнего угла рабочей области окна. Но для функции TrackPopupMenu требуются экранные координаты. Чтобы меню всплыло вблизи курсора мыши, определяют экранные координаты курсора. С этой целью вызывают функцию GetMessagePos и выделяют горизонтальную xPos и вертикальную yPos составляющие координат:

 

xyPos = GetMessagePos();

xPos = LOWORD(xyPos);

yPos = HIWORD(xyPos);

 

Далее обычным способом создают временное меню.

На последнем этапе выводят всплывающее меню:

 

TrackPopupMenu(hFloatMenu,

TPM_CENTERALIGN | TPM_LEFTBUTTON | TPM_VCENTERALIGN,

xPos, yPos, 0, hwnd, NULL);

 

Второй аргумент вызова и координаты xPos, yPos подобраны так, что после "всплытия" меню курсор мыши оказывается точно посредине меню.

После выбора строки меню автоматически исчезает, но перед этим посылает сообщение WM_COMMAND с кодом выбранной команды. При обработке этого сообщения на экран выдается сообщение о выбранной строке. Затем уничтожают созданное временное меню.

Акселераторы

Для быстрого доступа к командам используют акселераторы. Их иногда называют "клавишами быстрого вызова" команд меню. В действительности же акселераторы могут быть связаны с любыми командами.

При поступлении сообщения WM_COMMAND от акселератора младшее слово параметра wParam содержит идентификатор связанной с акселератором команды, а старшее слово wParam равно 1.

Операционная система широко использует акселераторы. Например, стандартное системное меню практически любого окна содержит строку "Закрыть Alt+F4" и команда этой строки связана с акселератором Alt+F4. То есть одновременное нажатие клавиш Alt и F4 равноценно выбору строки "Закрыть Alt+F4" системного меню активного окна.

Приложение все используемые акселераторы должно записать в одну таблицу и работать с дескриптором этой таблицы. Для работы с таблицей существует несколько функций.

Функция CreateAcceleratorTable создает таблицу акселераторов:

 

HACCEL CreateAcceleratorTable(LPACCEL lpaccl, int cEntries);

 

Здесь lpaccl – указатель на массив структур типа ACCEL, который содержит описания акселераторов, a cEntries – количество структур в массиве lpaccl. В случае успешного создания функция возвращает дескриптор созданной таблицы, иначе – NULL.

Каждую созданную функцией CreateAcceleratorTable таблицу нужно разрушать до завершения работы приложения. Для этого вызывают функцию DestroyAcceleratorTable.

Структура ACCEL задает данные одного акселератора и описана следующим образом:

 

typedef struct {

BYTE fVirt;

WORD key;

WORD cmd;

} ACCEL;

 

Назначение полей структуры ACCEL:

1. fVirt задает флажки акселератора и может быть комбинацией следующих значений:

Значение Пояснение
FALT Клавишу key нажимают при нажатой клавише Alt
FCONTROL Клавишу key нажимают при нажатой клавише Ctrl
FNOINVERT Определяет, что никакой верхнего уровня пункт меню не высвечен, когда акселератор используется. Если этот флажок не определен, верхнего уровня пункт меню будет высвечен, если возможно, когда акселератор используется
FSHIFT Клавиша key нажимают при нажатой клавише Shift
FVIRTKEY Поле key содержит код виртуальной клавиши. Если флажок FVIRTKEY не установлен, то предполагается, что поле key содержит код символа ASCII

2. key задает акселератор и может быть кодом виртуальной клавиши или символа ASCII. Список допустимых значений кода приведен в табл. 4.1.

3. cmd – идентификатор команды. Если нажать определяемую акселератором комбинацию клавиш, то функция окна получит сообщение WM_COMMAND или WM_SYSCOMMAND. При этом младшее слово параметра wParam будет содержать значение cmd.

Таблица 4.1. Список кодов виртуальных клавиш

Символическое имя Код виртуальной клавиши Клавиша на клавиатуре
Не определено 0x0  
VK_LBUTTON (код мыши) 0x1  
VK_RBUTTON (код мыши) 0x2  
VK_CANCEL 0x3 <Control + Break>
VK_MBUTTON (код мыши) 0x4  
Не определено 0x5 – 0x7  
VK_BACK 0x8 Клавиша забоя <Backspace>
VK_TAB 0x9 <Tab>
Не определено 0xa-0xb  
VK_CLEAR 0xc Соответствует клавише <5> дополнительной клавиатуры при выключенном режиме <Num Lock>
VK_RETURN 0xd <Enter>
Не определено 0xe – 0xf  
VK_SHIFT 0x10 <Shift>
VK_CONTROL 0x11 <Control>
VK_MENU 0x12 <Alt>
VK_PAUSE 0x13 <Pause>
VK_CAPITAL 0x14 <Caps Lock>
He определено 0x15 – 0x1a  
VK_ESCAPE 1b <Esc>
He определено 0x1c – 0x1f  
VK_SPACE 0x20 Клавиша пробела
VK_PRIOR 0x21 <PgUp>
VK_NEXT 0x22 <PgDn>
VK_END 0x23 <End>
VK_HOME 0x24 <Home>
VK_LEFT 0x25 Клавиша перемещения влево <Left>
VK_UP 0x26 Клавиша перемещения вверх <Up>
VK_RIGHT 0x27 Клавиша перемещения вправо <Right>
VK_DOWN 0x28 Клавиша перемещения вниз <Down>
VK_SELECT 0x29  
VK_PRINT 0x2a  
VK_EXECUTE 0x2b  
VK_SNAPSHOT 0x2c <PrtSc>
VK_INSERT 0x2d <Insert>
VK_DELETE 0x2e <Delete>
VK_HELP 0x2f  
He определено 0x30 <0>
- « - » - 0x31 <1>
- « - » - 0x32 <2>
- « - » - 0x33 <3>
- « - » - 0x34 <4>
- « - » - 0x35 <5>
- « - » - 0x36 <6>
- « - » - 0x37 <7>
- « - » - 0x38 <8>
- « - » - 0x39 <9>
- « - » - 0x3a-0x40  
- « - » - 0x41 <A>
- « - » - 0x42 <B>
- « - » - 0x43 <C>
- « - » - 0x44 <D>
- « - » - 0x45 <E>
- « - » - 0x46 <F>
- « - » - 0x47 <G>
- « - » - 0x48 <H>
- « - » - 0x49 <I>
- « - » - 0x4a <J>
- « - » - 0x4b <K>
- « - » - 0x4c <L>
- « - » - 0x4d <M>
- « - » - 0x4e <N>
- « - » - 0x4f <O>
- « - » - 0x50 <P>
- « - » - 0x51 <Q>
- « - » - 0x52 <R>
- « - » - 0x53 <S>
- « - » - 0x54 <T>
- « - » - 0x55 <U>
- « - » - 0x56 <V>
- « - » - 0x57 <W>
- « - » - 0x58 <X>
- « - » - 0x59 <Y>
- « - » - 0x5a <Z>
- « - » - 0x5b- 0x5f  
VK_NUMPAD0 0x60 <0> на цифровой клавиатуре
VK_NUMPAD1 0x61 <1> на цифровой клавиатуре
VK_NUMPAD2 0x62 <2> на цифровой клавиатуре
VK_NUMPAD3 0x63 <3> на цифровой клавиатуре
VK_NUMPAD4 0x64 <4> на цифровой клавиатуре
VK_NUMPAD5 0x65 <5> на цифровой клавиатуре
VK_NUMPAD6 0x66 <6> на цифровой клавиатуре
VK_NUMPAD7 0x67 <7> на цифровой клавиатуре
VK_NUMPAD8 0x68 <8> на цифровой клавиатуре
VK_NUMPAD9 0x69 <9> на цифровой клавиатуре
VK_MULTIPLAY 0x6a <*> на цифровой клавиатуре
VK_ADD 0x6b <+> на цифровой клавиатуре
VK_SEPARATOR 0x6c  
VK_SUBTRACT 0x6d <-> на цифровой клавиатуре
VK_DECIMAL 0х6e 0 на цифровой клавиатуре
VK_DIVIDE 0x6f </> на цифровой клавиатуре
VK_F1 0x70 <F1>
VK_F2 0x71 <F2>
VK_F3 0x72 <F3>
VK_F4 0x73 <F4>
VK_F5 0x74 <F5>
VK_F6 0x75 <F6>
VK_F7 0x76 <F7>
VK_F8 0x77 <F8>
VK_F9 0x78 <F9>
VK_F10 0x79 <F10>
VK_F11 0x7а <F11>
VK_F12 0x7b <F12>
VK_F13 0x7с  
VK_F14 0x7d  
VK_F15 0х7е  
VK_F16 0x7 f  
He определено 0x80 – 0x8f  
VK_NUMLOCK 0x90 <Num Lock>
VK_SCROLL 0x91 <Scroll Lock>
He определено 0x92 – 0xb9  
- « - » - 0xba ;
- « - » - 0xbb + =
- « - » - 0xbc , <
- « - » - 0xbd - _
- « - » - 0xbe . >
- « - » - 0xbf / ?
- « - » - 0xc0 ` ~
- « - » - 0xc1 – 0xda  
- « - » - 0xdb [ {
- « - » - 0xdc \ |
- « - » - 0xdd ] }
- « - » - 0xde ' "

 

Задача. Создать таблицу акселераторов для генерирования восьми наиболее часто используемых команд.

Следующий фрагмент содержит образец решения данной задачи.

 

#define CM_FILE_NEW 1000

#define CM_FILE_OPEN 1001

#define CM_FILE_SAVE 1002

#define CM_FILE_QUIT 1003

#define CM_EDIT_CUT 2000

#define CM_EDIT_PASTE 2001

#define CM_EDIT_COPY 2002

#define CM_EDIT_DEL 2003

 

HACCEL CreateAccelTable(void)

{

//Массив акселераторов

ACCEL Accel[8];

//Создать

Accel[0].fVirt = FVIRTKEY | FCONTROL;

Accel[0].key = 0x4e;

Accel[0].cmd = CM_FILE_NEW;

//Открыть

Accel[1].fVirt = FVIRTKEY | FCONTROL;

Accel[1].key = 0x4f;

Accel[1].cmd = CM_FILE_OPEN;

//Сохранить

Accel[2].fVirt = FVIRTKEY | FCONTROL;

Accel[2].key = 0x53;

Accel[2].cmd = CM_FILE_SAVE;

//Выход

Accel[3].fVirt = FVIRTKEY | FALT;

Accel[3].key = 0x73;

Accel[3].cmd = CM_FILE_QUIT;

//Вырезать

Accel[4].fVirt = FVIRTKEY | FCONTROL;

Accel[4].key =0x58;

Accel[4].cmd = CM_EDIT_CUT;

//Вклеить

Accel[5].fVirt = FVIRTKEY | FCONTROL;

Accel[5].key = 0x56;

Accel[5].cmd = CM_EDIT_PASTE;

//Копировать

Accel[6].fVirt = FVIRTKEY | FCONTROL;

Accel[6].key = 0x43;

Accel[6].cmd = CM_EDIT_COPY;

//Удалить

Accel[7].fVirt = FVIRTKEY;

Accel[7].key = 0x2e;

Accel[7].cmd = CM_EDIT_DEL;

return CreateAcceleratorTabIe( (LPACCEL)Accel, 8);

}

 

Проанализируем этот фрагмент.

1. Задают идентификаторы команд, с которыми связаны акселераторы:

 

#define CM_FILE_NEW 1000

...

#define CM_EDIT_DEL 2003

 

2. Описывают функцию, которая заполняет массив акселераторами, создает таблицу и возвращает дескриптор созданной таблицы. Заголовок функции имеет вид:

 

HACCEL CreateAccelTable( void)

 

2.1. Описывают массив акселераторов:

 

ACCEL Ассеl[8];

 

2.2. Для каждого элемента массива задают данные. Например, первую структуру в массиве заполняют следующим образом:

 

//Создать

Accel[0].fVirt = FVIRTKEY | FCONTROL;

Accel[0].key = 0х4е;

Accel[0].cmd = CM_FILE_NEW;

 

Рассмотрим, для чего и каким образом выбираются эти данные. Пусть раздел "Файлы" главного меню окна содержит элемент "Создать" с идентификатором команды CM_FILE_NEW и при выборе этой строки функция окна получает сообщение WM_COMMAND с параметром wParam, младшее слово которого содержит идентификатор CM_FILE_NEW.

В приложениях выбор строки создания нового файла (документа, проекта и т. д.) обычно дублируют сочетанием клавиш Ctrl+N. Для создания такого же акселератора для строки "Создать", в поле fVirt записано значение FCONTROL. Это означает, что клавиша, заданная полем key, должна быть нажата при нажатой клавише Ctrl. В поле key записан код выбранной клавиши – 0х4е. Как видно из табл. 4.1, этому коду соответствует клавиша <N>.

Для того чтобы акселератор, состоящий из комбинации клавиши <N> и клавиши Ctrl, работал вне зависимости от состояния клавиши <Caps Lock>, в поле key записан виртуальный код. Об этом отдельно сообщается в поле fVirt – оно содержит значение FVIRTKEY. Виртуальный код имеет свои преимущества. Например, при использовании кодов ASCII акселератор активизировался бы только при установке режима заглавных букв с помощью клавиши <Caps Lock>.

Так же задают значения остальных элементов массива.

2.3. Указатель на массив и количество элементов массива передают функции CreateAcceleratorTable:

 

CreateAcceleratorTable( (LPACCEL)Accel, 8);

 

Эта функция в случае успешного выполнения возвращает дескриптор созданной таблицы акселераторов, иначе – NULL.

2.4. Возвращенное функцией CreateAcceleratorTable возвращают из функции CreateAccelTable:

 

return CreateAcceleratorTable( (LPACCEL)Accel, 8);

 

Для того чтобы при нажатии акселераторов операционная система могла формировать соответствующие сообщения, вызывают функцию TranslateAccelerator:

 

int TranslateAccelerator( HWND hwnd, HACCEL hAccTable, LPMSG lpMsg );

 

Эта функция транслирует сообщение WM_KEYDOWN или WM_SYSKEYDOWN в сообщение вида к WM_COMMAND или WM_SYSCOMMAND и затем посылает сообщение WM_COMMAND или WM_SYSCOMMAND непосредственно функции окна hwnd. Она завершает работу только после обработки сообщения. В случае успешного выполнения функция возвращает ненулевое значение.

Параметры:

1. hwnd – дескриптор окна.

2. hAccTable – дескриптор таблицы акселераторов. Эта таблица должна быть или загружена вызовом функции LoadAccelerators или создана функцией CreateAcceleratorTable.

3. lpMsg – указатель на структуру типа MSG, выбранный из очереди сообщений функцией GetMessage или PeekMessage.

Для отличения сообщения функции TranslateAccelerator от сообщений других источников (например, от меню) операционная система в старшее слово параметра wParam сообщения WM_COMMAND или WM_SYSCOMMAND записывает 1.

Сообщения акселераторов, дублирующих строки системного меню, транслируют в сообщение WM_SYSCOMMAND. Сообщения других акселераторов транслируют в сообщение WM_COMMAND.

Если функция TranslateAccelerator вернула ненулевое значение, это означает, что сообщение обработано функцией указанного окна. И это сообщение не нужно передавать функции TranslateMessage или DispatchMessage.

Перечислим сходства и отличия использования акселератора от процесса выбора строки меню:

1. Если команда акселератора соответствует команде элемента меню, то при нажатии акселератора функция окна получает сообщения WM_INITMENU и WM_INITMENUPOPUP так же, как и при выборе строки меню с помощью мыши.

2. При нажатии акселератора функция окна не получит сообщений WM_INITMENU и WM_INITMENUPOPUP в следующих случаях:

- окно заблокировано;

- элемент меню заблокирован;

- акселератор не соответствует элементу системного меню и это окно свернуто;

- поступают сообщения от мыши.

3. Если указанное в вызове функции TranslateAccelerator окно активно и другие окна не имеют фокуса ввода (это может быть, если указанное окно свернуто), то TranslateAccelerator вместо WM_KEYUP или WM_KEYDOWN транслирует сообщение WM_SYSKEYUP или WM_SYSKEYDOWN.

4. Если указанное функции TranslateAccelerator окно свернуто, то она посылает сообщение WM_COMMAND, только если нажатый акселератор не связан с командой ни одного из элементов меню указанного окна.

Для использования акселераторов цикл обработки сообщений нужно изменить следующим образом:

 

HACCEL hAccel=CreateAccelTable();

...

while (GetMessage(&msg, 0, 0, 0))

{

if (!hAccel || !TranslateAccelerator(hwnd, hAccel, &msg))

{

TranslateMessage(&msg); DispatchMessage(&msg);

}

}

 

В этом фрагменте переменная hAccel содержит дескриптор созданной таблицы акселераторов. Если hAccel не равен NULL, вызывается функция TranslateAccelerator. Эта функция ищет в очереди сообщений сообщения от клавиатуры, соответствующие определенным в таблице акселераторам, преобразует такие сообщения в сообщения WM_COMMAND и WM_SYSCOMMAND и посылает их в функцию окна, минуя очередь сообщений приложения.

Младшее слово параметра wParam в последних двух сообщениях равно идентификатору, указанному в таблице акселераторов для данной комбинации клавиш. Старшее слово параметра wParam содержит 1 для сообщений, которые пришли от акселераторов, и 0 для сообщений, которые пришли от меню.

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

Листинг 4.6. Акселераторы в меню.

#include <windows.h>

#include <tchar.h>

 

#define CM_FILE_NEW 1000

#define CM_FILE_OPEN 1001

#define CM_FILE_SAVE 1002

#define CM_FILE_QUIT 1003

#define CM_EDIT_CUT 2000

#define CM_EDIT_PASTE 2001

#define CM_EDIT_COPY 2002

#define CM_EDIT_DEL 2003

#define CM_HELP_HELP 3000

#define CM_HELP_ABOUT 3001

 

BOOL RegClass(WNDPROC, LPCTSTR, UINT);

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HACCEL CreateAccelTable( void);

 

HINSTANCE hInstance;

TCHAR szClass[] = TEXT("AccelClass");

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

MSG msg;

HWND hwnd;

hInstance = hInst;

if (!RegClass(WndProc, szClass, COLOR_WINDOW)) return FALSE;

hwnd = CreateWindow(szClass, TEXT("Акселераторы"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

if (!hwnd) return FALSE;

HACCEL hAccel = CreateAccelTable();

while(GetMessage(&msg, 0, 0, 0))

{

if (!hAccel || !TranslateAccelerator(hwnd, hAccel, &msg))

{ TranslateMessage(&msg); DispatchMessage(&msg); }

}

DestroyAcceleratorTable(hAccel);

return msg.wParam;

}

 

BOOL RegClass(WNDPROC Proc, LPCTSTR szName, UINT brBackground)

{

WNDCLASS wc;

wc.style = wc.cbClsExtra = wc.cbWndExtra = 0;

wc.lpfnWndProc = Proc;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(brBackground+1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szName;

return (RegisterClass(&wc) != 0);

}

 

BOOL CreateMenuItem(HMENU hMenu, TCHAR *str, UINT uIns, UINT uCom, HMENU hSubMenu, BOOL flag, UINT fType)

{

MENUITEMINFO mii;

mii.cbSize = sizeof(MENUITEMINFO);

mii.fMask = MIIM_STATE | MIIM_TYPE | MIIM_SUBMENU | MIIM_ID;

mii.fType = fType;

mii.fState= MFS_ENABLED;

mii.dwTypeData = str;

mii.cch = sizeof(str);

mii.wID = uCom;

mii.hSubMenu = hSubMenu;

return InsertMenuItem(hMenu, uIns, flag, &mii);

}

 

HACCEL CreateAccelTable( void)

{

//Массив акселераторов

ACCEL Accel[8];

//Создать

Accel[0].fVirt= FVIRTKEY | FCONTROL;

Accel[0].key = 0x4e;

Accel[0].cmd = CM_FILE_NEW;

//Открыть

Accel[1].fVirt= FVIRTKEY | FCONTROL;

Accel[1].key = 0x4f;

Accel[1].cmd = CM_FILE_OPEN;

//Сохранить

Accel[2].fVirt= FVIRTKEY | FCONTROL;

Accel[2].key = 0x53;

Accel[2].cmd = CM_FILE_SAVE;

//Выход

Accel[3].fVirt= FVIRTKEY | FALT;

Accel[3].key = 0x73;

Accel[3].cmd = CM_FILE_QUIT;

//Вырезать

Accel[4].fVirt= FVIRTKEY | FCONTROL;

Accel[4].key = 0x58;

Accel[4].cmd = CM_EDIT_CUT;

//Вклеить

Accel[5].fVirt= FVIRTKEY | FCONTROL;

Accel[5].key = 0x56;

Accel[5].cmd = CM_EDIT_PASTE;

//Копировать

Accel[6].fVirt= FVIRTKEY | FCONTROL;

Accel[6].key = 0x43;

Accel[6].cmd = CM_EDIT_COPY;

//Удалить

Accel[7].fVirt= FVIRTKEY;

Accel[7].key = 0x2e;

Accel[7].cmd = CM_EDIT_DEL;

return CreateAcceleratorTable((LPACCEL)Accel, 8);

}

 

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

static HMENU hMainMenu, hFileMenu, hEditMenu, hHelpMenu;

switch (msg)

{

case WM_CREATE:

{

hMainMenu = CreateMenu();

SetMenu(hwnd, hMainMenu);

//Создаем временное меню для раздела "Файлы"

hFileMenu = CreatePopupMenu();

int i=0; //Инициализация позиции в меню

CreateMenuItem(hFileMenu, TEXT("&Новый\tCtrl+N"), i++, CM_FILE_NEW, NULL, FALSE, MFT_STRING);

CreateMenuItem(hFileMenu, TEXT("&Открыть\t Ctrt+O"), i++, CM_FILE_OPEN, NULL, FALSE, MFT_STRING);

CreateMenuItem(hFileMenu, TEXT("&Coxpaнить\t Ctrl+S"), i++, CM_FILE_SAVE, NULL, FALSE, MFT_STRING);

CreateMenuItem(hFileMenu, NULL, i++, 0, NULL, FALSE, MFT_SEPARATOR);

CreateMenuItem(hFileMenu, TEXT("В&ыход\t Alt+F4"), i++, CM_FILE_QUIT, NULL, FALSE, MFT_STRING);

hEditMenu = CreatePopupMenu();

i=0; //Инициализация позиции в меню

CreateMenuItem(hEditMenu, TEXT("&Вырезать\t Ctrl+X"), i++, CM_EDIT_CUT, NULL, FALSE, MFT_STRING);

CreateMenuItem(hEditMenu, TEXT("B&ставить\t Ctrl+V"), i++, CM_EDIT_PASTE, NULL, FALSE, MFT_STRING);

CreateMenuItem(hEditMenu, TEXT("&Копировать\t Ctrl+C"), i++, CM_EDIT_COPY, NULL, FALSE, MFT_STRING);

CreateMenuItem(hEditMenu, TEXT("&Удалить\t Delete"), i++, CM_EDIT_DEL, NULL, FALSE, MFT_STRING);

hHelpMenu = CreatePopupMenu();

i=0; //Инициализация позиции в меню

CreateMenuItem(hHelpMenu, TEXT("&Помощь"), i++, CM_HELP_HELP, NULL, FALSE, MFT_STRING);

CreateMenuItem(hHelpMenu, NULL, i++, 0, NULL, FALSE, MFT_SEPARATOR);

CreateMenuItem(hHelpMenu, TEXT("&О программе"), i++, CM_HELP_ABOUT, NULL, FALSE, MFT_STRING);

//Подключаем временные меню к главному меню

i=0; //Инициализация позиции в меню

CreateMenuItem(hMainMenu, TEXT("&Файл"), i++, 0, hFileMenu, FALSE, MFT_STRING);

CreateMenuItem(hMainMenu, TEXT("&Правка"), i++, 0, hEditMenu, FALSE, MFT_STRING);

CreateMenuItem(hMainMenu, TEXT("&Помощь"), i++, 0, hHelpMenu, FALSE, MFT_STRING);

DrawMenuBar(hwnd);

return 0;

}

case WM_COMMAND:

{

TCHAR str[30]={0};

if (HIWORD(wParam)==1)

_tcscpy(str, TEXT("Сообщение от акселератора"));

else _tcscpy(str, TEXT("Сообщение от меню"));

switch (LOWORD(wParam))

{

case CM_FILE_NEW:

{ MessageBox(hwnd, TEXT("Создать"), str, MB_OK); return 0; }

case CM_FILE_OPEN:

{ MessageBox(hwnd, TEXT("Открыть"), str, MB_OK); return 0; }

case CM_FILE_SAVE:

{ MessageBox(hwnd, TEXT("Сохранить"), str, MB_OK); return 0; }

case CM_FILE_QUIT: { DestroyWindow(hwnd); return 0; }

case CM_EDIT_CUT:

{ MessageBox(hwnd, TEXT("Вырезать"), str, MB_OK); return 0; }

case CM_EDIT_PASTE:

{ MessageBox(hwnd, TEXT("Вклеить"), str, MB_OK); return 0; }

case CM_EDIT_COPY:

{ MessageBox(hwnd, TEXT("Копировать"), str, MB_OK); return 0; }

case CM_EDIT_DEL:

{ MessageBox(hwnd, TEXT("Удалить"), str, MB_OK); return 0; }

case CM_HELP_HELP:

{ MessageBox(hwnd, TEXT("Помощь"), TEXT("Помощь"), MB_OK); return 0; }

case CM_HELP_ABOUT:

{ MessageBox(hwnd, TEXT("Демонстрация подключения акселераторов"), TEXT("О программе"), MB_OK); return 0; }

}

}

case WM_DESTROY: { PostQuitMessage(0); return 0;}

}

return DefWindowProc(hwnd, msg, wParam, lParam);

}

 

Проанализируем те изменения в этом приложении, которые обусловлены включением работы с акселераторами.

1. Главная функция содержит описание дескриптора таблицы акселераторов, трансляцию сообщений акселераторов в цикле обработки сообщений и разрушение таблицы перед завершением работы приложения:

 

HACCEL hAccel = CreateAccelTable();

while(GetMessage(&msg, 0, 0, 0))

{

if (!hAccel || !TranslateAccelerator(hwnd, hAccel, &msg))

{ TranslateMessage(&msg); DispatchMessage(&msg); }

}

DestroyAcceleratorTable(hAccel);

 

Дескриптор таблицы акселераторов должен быть описан до цикла обработки сообщений. Здесь таблица создается единственный раз при запуске приложения. Если таблица создается в другом месте или модифицируется при обработке тех или иных сообщений, то этот дескриптор нужно рписывать глобально. Например, до описания главной функции нужно записать оператор вида

 

HACCEL hAccel;

 

Тогда в любом месте приложения можно построить новую таблицу акселераторов и запомнить их дескриптор обычным способом. Например, если есть функция с именем CreateAccelTable1, то можно записать так:

 

hAccel=CreateAccelTable1();

 

2. Функция CreateAccelTable была подробно рассмотрена выше. Таких функций может быть несколько, если требуется модифицировать таблицу акселераторов в процессе работы приложения.

3. Для различения сообщения от акселераторов используют значение старшего слова параметра wParam. При поступлении сообщения WM_COMMAND от акселератора это значение равно 1.

Нижеследующий фрагмент анализирует это значение и составляет заголовок окна сообщения в зависимости от того, каким образом было послано сообщение:

 

char str[30];

if (HIWORD(wParam)==1)

strcpy(str, "Сообщение от акселератора");

else strcpy(str, "Сообщение от меню");

Контрольные вопросы

1. Перечислите основные виды меню и укажите различия между ними.

2. Запишите алгоритм создания главного меню, состоящего из двух разделов, если в каждом разделе строки связаны с командами.

3. Для чего в цикле обработки сообщений в листинге 4.1 вызывают функцию TranslateMessage?

4. Какие меню посылают сообщения функции окна и какие меню нужно разрушать при обработке сообщения WM_DESTROY?

5. Каким образом можно послать сообщение WM_COMMAND с идентификатором команды, если связанная с этой командой строка заблокирована?

6. Чем отличаются плавающие меню от остальных видов меню?

7. Чем отличается генерация команды с помощью акселератора от генерации команды путем выбора строки меню?

8. В каком порядке нужно описать основные объекты приложения, если таблица акселераторов изменяется при обработке сообщений?

9. В чем преимущество использования виртуального кода в акселераторах?

Упражнения

1. Главное меню содержит строки "Невидимый курсор", "Обычный курсор" и "Выход". Создать плавающее меню с такими же строками. Чтобы курсор стал невидимым, вызвать функцию ShowCursor(0); видимым – ShowCursor(1).

2. Раздел "Пользователи" главного меню содержит список пользователей. При выборе пользователя в главном меню появляются дополнительные разделы. При смене пользователя меняются и эти разделы.

3. При открытии или создании документа появляется раздел "Правка" с единственной командой "Выделить". После выбора этой команды в этом разделе добавляются строки "Удалить" и "Копировать", а команда "Выделить" отмечается галочкой (которая убирается при повторном выборе). Если выбрать команду "Копировать", то элемент "Удалить" заменяется элементом "Вставить".

4. Главное меню содержит раздел "Файл", в котором перечислены строки с именами команд "Создать", "Открыть" и "Выход". После выбора строк "Создать" или "Открыть" добавить строки "Сохранить" и "Печать", а также раздел "Правка" со строками "Вырезать", "Вклеить" и "Копировать". Команды связать с акселераторами.

5. Главное меню содержит раздел "Файл" с именами команд "Создать", "Открыть", "Сохранить", "Закрыть", "Печать" и "Выход", а также раздел "Правка" со строками "Вырезать", "Вклеить" и "Копировать". После выбора команды "Закрыть" удалить раздел "Правка". Команды связать с акселераторами.

6. Главное меню содержит раздел "Рисунок" с именами четырех геометрических фигур. После выбора фигуры отобразить фигуру в определенной части окна. При нажатии правой клавиши мыши над любой отображенной фигурой на месте нажатия отобразить плавающее меню с соответствующими выбранной фигуре командами.

7. Главное меню содержит раздел "Файл" со строками "Создать", "Открыть" и "Выход". При выборе строки "Создать" или "Открыть" создать окно, которое содержит меню с разделами "Правка" и "Эффекты". Команды связать с акселераторами.

8. Главное меню содержит раздел "Файл" со строками "Создать", "Открыть" и "Выход". При выборе строк "Создать" или "Открыть" создать окно, которое содержит раздел меню "Фигуры" со списком имен геометрических фигур; при выборе имени отобразить фигуру с таким именем и пометить имя галочкой. При повторном выборе имени убрать фигуру и удалить галочку.

9. Главное меню содержит раздел "Фигуры" с именами геометрических фигур. При выборе названия фигуры в главное меню добавить раздел с названием фигуры и перечислением основных ее параметров в этом разделе. При повторном нажатии должны исчезнуть этот раздел и галочка.

10. Строки главного меню расположить в нескольких линиях, а строки плавающего меню – в одну линию.

11. Создать плавающее меню для выбора и установки вида курсора мыши. При выборе имени вида курсора курсор мыши должен принять соответствующий вид. Для загрузки и показа курсора использовать операторы вида

HCURSOR hCursor=LoadCursor(NULL, IDC_CROSS); SetCursor(hCursor);

12. Главное меню содержит раздел "Файл" со строками "Новый", "Открыть" и "Выход" и раздел "Помощь" со строками "Содержание" и "О программе". При выборе строки "Содержание" появляются строки "Введение", "Часть 1", "Часть 2" ... а при выборе строки "Часть ..." появляются строки "Раздел 1", "Раздел 2"...

13. Рабочую область окна полностью занимают два временных окна. Главное меню первого окна содержит раздел "Файл" со строками "Открыть" и "Выход". Если выбрать строку "Открыть", то во втором окне появляется главное меню с разделом "Правка".

14. Плавающее меню содержит строки "Спрятать", "Показать", "Масштаб", "Свойства". Строка "Масштаб" указывает на временное меню из четырех зависимых строк: "50%", "100%", "150%" и "200%", при выборе одна из которых отмечается кружочком.

15. Рабочую область окна приложения занимают два временных окна. Главное меню первого временного окна содержит раздел "Файл" с командами "Создать", "Открыть", "Демо-версия" и "Выход". Причем состоянием строки "Демо-версия" управляет второе окно.

16. Главное меню содержит раздел "Файл" со строками "Создать", "Открыть" и "Выход". При выборе команды "Создать" или "Открыть" добавить раздел "Правка" со строками "Вырезать", "Вклеить" и "Копировать". Команды только отображаемых строк связать с акселераторами.

17. Главное меню содержит раздел "Файл", в котором перечислены строки с именами команд "Создать", "Открыть" и "Выход", которые могут быть отмечены как зависимые переключатели. После выбора строк "Создать" или "Открыть" добавить раздел "Правка" с командами "Вырезать", "Вклеить" и "Копировать", которые могут быть отмечены как независимые флажки. Команды отмеченных строк связать с акселераторами.

18. Главное меню содержит раздел "Фигуры" с зависимым списком имен геометрических фигур. При выборе имени должна быть отображена только эта фигура и отмечено кружочком только это имя.

19. Раздел "Файл" содержит строки "Создать", "Открыть", "Демо- версия" и "Выход". При выборе строки "Создать" или "Открыть" создать перекрывающееся окно с разделом меню "Эффекты". Список строк раздела "Эффекты" зависит от состояния строки "Демо-версия". Команды отображенных строк меню связать с акселераторами.

20. Главное меню содержит раздел "Файл" со строкой "Открыть". При выборе этой строки в главное меню добавить раздел "Правка" со строками "Вырезать", "Копировать" и "Удалить", удалить строку "Открыть" и добавить строку "Закрыть". При выборе строки "Закрыть" вернуться к исходному состоянию. Команды отображенных строк меню связать с акселераторами.

21. Рабочую область окна приложения полностью занимает временное окно с пустым главным меню. Главное меню окна приложения содержит раздел "Файл" со строками "Открыть" и "Закрыть" (заблокирована). При выборе команды "Открыть" создать главное меню временного окна с разделом "Правка" со строками "Вырезать", "Копировать" и "Удалить". После этого заблокировать строку "Открыть" и разблокировать команду "Закрыть". При выборе строки "Закрыть" вернуться к исходному состоянию.

22. На месте нажатия правой клавиши мыши всплывает меню. Если курсор мыши ближе к верхнему или нижнему краю рабочей области, то строки меню выстроить в линию, иначе – в столбик.

23. Главное меню содержит раздел "Пользователи", в котором перечислены строки с именами типов пользователей. После выбора типа пользователя этот раздел исчезает и появляется раздел "Данные", в котором перечислены общие для всех типов пользователей и типичные только для выбранного типа строки данных. Команды отображенных строк связать с акселераторами.

24. Главное меню содержит раздел "Цвета" с пятью именами стандартных цветов Windows и раздел "Фигуры" с именами трех плоских фигур. После выбора цвета и фигуры отобразить фигуру выбранным цветом, а соответствующие строки меню отметить галочкой. При повторном нажатии должны исчезнуть эта фигура и галочки.

25. На месте нажатия правой клавиши мыши отобразить плавающее меню, отмеченные галочкой элементы которого указывают на временные меню, строки которых служат зависимыми переключателями.