Меню
В этом туториале мы научимся, как вставить в наше окно меню.
Скачайте пример 1 и пример 2.
Теория:
Меню - это один из важнейших компонентов вашего окна. Меню является списком всех возможностей, которые программа предлагает пользователю. Пользователь не обязан читать мануал, поставляемый с программой, чтобы использовать ее (весьма спорная точка зрения - прим. пер.), он может досконально исследовать меню, чтобы получить представление о возможностях данной программы и начать 'играть' с ней немедленно. Так как меню - это инструмент для того, чтобы дать пользователю 'быстрый старт', вы должны следовать стандарту.
Короче говоря, первые два пункта меню должны быть "File" и "Edit", а последний - "Help". Вы можете вставить ваши собственные пункты между "Edit" и "Help". Если пункт меню вызывает диалоговое окно, вам нужно заканчивать название пункта эллипсисом (...).
Меню - это разновидность ресурсов. Есть несколько видов ресурсов, таких как диалоговые окна, строковые таблицы, иконки, битмапы, меню и т.д. ресурсы описываются в отдельном файле, называющемся файлом ресурсов, который, как правило, имеет расширение .rc. Вы можете соединять ресурсы с исходным кодом во время стадии линковки. Окончательный продукт - это исполняемый файл, который содержит как инструкции, так и ресурсы.
Вы можете писать файлы ресурсов, используя любой текстовый редактор. Они состоят из набора фраз, определяющих внешний вид и другие атрибуты ресурсов, используемых в программе. Хотя вы можете писать файлы ресурсов в текстовом редакторе, это довольно тяжело. Лучшей альтернативой является использование редактора ресурсов, который позволит вам визуально создавать дизайн ваших ресурсов. редакторы ресурсов обычно входят в пакет с компиляторами, такими как Visual C++, Borland C++ и т.д.
Вы описываете ресурс меню примерно так:
MyMenu MENU { [menu list here] }
Си-программисты могут заметить, что это похоже на объявление структуры. MyMenu - это имя меню, за ним следует ключевое слово MENU и список пунктов меню, заключенный в фигурные скобки. Вместо них вы можете использовать BEGIN и END. Этот вариант больше понравится программистам на Паскале.
Список меню включает в себя выражения 'MENUITEM' или 'POPUP'.
'MENUITEM' определяет пункт меню, который не является подменю. Его синтаксис следующий:
MENUITEM "&text", ID [,options]
Выражение начинается ключевым словом 'MENUITEM', за который следует текст, который будет отображаться. Обратите внимание на амперсанд. Его действие заключается в том, что следующий за ним символ будет подчеркнут. Затем идет строка в качестве ID пункта меню. ID - это номер, который будет использоваться для обозначения пункта меню в сообщении, посылаемое процедуре окно, когда этот пункт меню будет выбран. Каждое ID должно быть уникальным.
Опции опциональны. Доступны следующие опции:
- *GRAYED - пункт меню неактивен, и он не генерирует сообщение WM_COMMAND. Текст серого цвета.
- *INACTIVE - пункт меню неактивен, и он не генерирует сообщение WM_COMMAND. Текст отображается нормально.
- *MENUBREAK - этот пункт меню и последующие пункты отображаются после новой линии меню.
- *HELP - этот пункт меню и последующие пункты выравнены по правой стороне.
Вы можете использовать одну из вышеописанных опций или комбинировать их оператором "or". Учтите, что 'INACTIVE' и 'GRAYED' не могут комбинироваться вместе. Выражение 'POPUP' имеет следующий синтаксис:
POPUP "&text" [,options] { [menu list] }
Выражение 'POPUP' определяет пункт меню, при выборе которого выпадает список пунктов в маленьком рoрuр-окне. Список меню может быть выражением 'MENUITEM' или 'POPUP'. Есть специальный вид выражения 'MENUITEM' - 'MENUITEM SEPARATOR', который отрисовывает горизонтальную линию в popup-окне.
Последний шаг - это ссылка на ваш скрипт ресурса меню в программе.
Вы можете сделать это в двух pазных местах.
- В члене lрszMenuName структуры WNDCLASSEX. Скажем, если у вас было меню под названием "FirstMenu", вы можете присоединить меню к вашему окну следующим образом:
.DATA MenuName db "FirstMenu",0 ........................... ........................... .CODE ........................... mov wc.lpszMenuName, OFFSET MenuName ...........................
.DATA MenuName db "FirstMenu",0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,\ hMenu,\ hInst,\ NULL\ ...........................
Вы можете спросить, в чем разница между этими двумя методами? Когда вы делаете ссылку на меню в структуре WNDCLASSEX, меню становится меню по умолчанию для данного класса окна. Каждое окно этого класса будет иметь такое меню.
Если вы хотите, чтобы каждое окно, созданное из одного класса, имело разное меню, вы можете выбрать второй подход. В этом случае, любое окно, которому передается хэндл меню в функции CreateWindowEx будет иметь меню, которое замещает меню по умолчанию, указанное в структуре WNDCLASSEX. Сейчас мы узнаем, как меню уведомляет процедуру окна о том, что пользователь выбрал пункт меню.
Когда пользователь выберет пункт меню, процедура окна получит сообщение WM_COMMAND. Hижнее слово wParam'а содержит ID выбранного пункта меню.
Теперь у нас достаточно информации для того, чтобы создать и использовать меню. Давайте сделаем это.
Пpимеp:
Первый пример показывает нам как создать и использовать меню, указав имя меню в классе окна.
.386 .model flat,stdcall option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
.data
ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0
.data?
hInstance HINSTANCE ? CommandLine LPSTR ?
.const
IDM_TEST equ 1 ; Menu IDs IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine
mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name here mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax
invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp
end start
*****************************************************************************
Menu.rc
*****************************************************************************
#define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4
FirstMenu MENU { POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT }
MENUITEM "&Test", IDM_TEST }
Анализ:
Давайте сначала проанализируем файл ресурсов.
#define IDM_TEST 1 /* все pавно, что IDM_TEST equ 1*/ #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4
Вышенаписанные линии определяют ID пунктов меню. Вы можете присваивать ID любое значение, главное, чтобы оно было уникально.
FirstMenu MENU
Определите ваше меню ключевым словом 'MENU'.
POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT }
Определите рoрuр-меню с четырьмя пунктами меню, третье - это сепаратор.
MENUITEM "&Test", IDM_TEST
Определите пункт меню в основном меню.
Далее мы изучим исходный код.
MenuName db "FirstMenu",0 ; Имя нашего меню в файле ресурсов Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0
'MenuName' - это имя меню в файле ресурсов. Заметьте, что вы можете определить более, чем одно меню в файле ресурсов, поэтому вы можете указывать, какое меню хотите использовать. Остающиеся три линии определяют текстовые строки, которые будут отображаться в messagebox'е при выборе соответствующего пункта меню пользователем.
IDM_TEST equ 1 ; ID меню IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4
Определите ID меню для использования в процедуре окна. Эти значения должны совпадать с теми, что были определены в файле ресурсов.
.ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF
В процедуре окна мы обрабатываем сообщение WM_COMMAND. Когда пользователь выбирает пункт меню, его ID посылается процедуре окна в нижнем слове wрaram'а вместе с сообщением WM_COMMAND. Поэтому, когда мы сохраняем значение wрaram в eax, мы сравниваем значение в ax с ID пунктов меню, определенными ранее, и поступаем соответствующим образом. В первых трех случаях, когда пользователь выбирает 'Test', 'Say Hell' и 'Say GoodBye', мы отображаем текстовую строку в messagebox'е.
Если пользователь выбирает пункт 'Exit', мы вызываем DestroyWindow с хэндлом нашего окна в качестве его параметра, которое закрывает наше окно.
Как вы можете видеть, указание имени меню в классе окна довольно просто и прямолинейно. Тем не менее, вы также можете использовать альтернативный метод для того, чтобы загружать меню в ваше окно. Я не буду воспроизводить здесь весь исходный код. Файл ресурсов такой же. Есть небольшие изменения в исходнике, которые я покажу ниже.
.data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ? ; handle of our menu
Определите переменную типа HMENU, чтобы сохранить хэндл нашего меню.
invoke LoadMenu, hInst, OFFSET MenuName mov hMenu,eax INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\ hInst,NULL
Перед вызовом CreateWindowEx, мы вызываем LoadMenu, передавая ему хэндл процесса и указатель на имя меню. LoadMenu возвращает хэндл нашего меню, который мы передаем CreateWindowEx.
[C] Iczelion, пер. Aquila.