Составной частью проекта, работа которого планируется в Windows, является файл определения ресурсов. Возникает вопрос: что же такое ресурсы, когда и в каких целях они используются?
У Windows есть некоторые предопределенные данные (предопределенные курсоры, иконки и кисти).
Точно так же, почти в каждой программе для Windows есть некоторые данные, которые определяются
еще до начала работы программы, особым образом добавляются в выполняемый файл и используются
при работе программы. Яркими примерами таких данных являются иконки и курсоры мыши. Кроме
них, к числу ресурсов относятся:
- используемые в программе изображения;
- строки символов;
- меню;
- ускорители клавиатуры;
- диалоговые окна;
- шрифты;
- ресурсы, определяемые пользователем;
Следует отметить, что выполняемым файлом может быть файл программы .exe, файл динамической библиотеки .dll и другие бинарные файлы. Для удобства буду их называть bin-файлами.
Помимо того, что ресурсы определяются до начала работы программы и добавляются в bin-файл, у них есть еще одна характерная черта. При загрузке bin-файла в память, РЕСУРСЫ В ПАМЯТЬ НЕ ЗАГРУЖАЮТСЯ. Только в случае, если тот или иной ресурс требуется для работы программы, программа сама загружает ресурс в память.
Возможность использования того или иного атрибута в качестве ресурса не означает, что программист не может создавать эти атрибуты в программе. Яркий пример тому можно найти в работе старого доброго Program Manager'а. При перетаскивании иконки с место на место курсор меняет свою форму и принимает форму, подобную перетаскиваемой иконке. Естественно, что в этом случае курсоры определяются программой. Помимо этого, вспомним drag-and-drop в Explorer'е и изменение формы курсора при этом.
Еще одним примером являются динамические меню, т.е. меню, которые изменяют свой вид и
предоставляемые возможности в зависимости от обстоятельств.
Ресурсы стандартные и нестандартные
Все ресурсы, заранее определенные в Win32 API, называются стандартными. Для работы с ними существуют специальные функции. Но именно эта стандартность и ограничивает возможности программиста. Стандарт, он и есть стандарт.
Для того чтобы можно было преодолеть эти ограничения, был создан особый тип ресурсов -
определяемые пользователем ресурсы. Используя именно этот тип, мы можем предоставить в
распоряжение программы практически любые данные. Но, как известно, бесплатным бывает только
сыр в мышеловке. В данном случае платой за универсальность является усложнение программы,
так как забота о манипулировании данными из ресурсов лежит уже не на системе, а на программе,
использующей эти ресурсы. Программа может только получить указатель на данные ресурсов,
загруженные в память средствами Windows. Дальнейшая работа с ними ложится ИСКЛЮЧИТЕЛЬНО на
плечи программы!
Ресурсы создаются отдельно от файлов программы и добавляются в bin-файл при линковании программы. Подавляющее большинство ресурсов содержится в файлах ресурсов, имеющих расширение .RC. Имя файла ресурсов обычно совпадает с именем bin-файла программы. Так, если имя программы MYPROG.EXE, то имя файла ресурсов - MYPROG.RC.
Некоторые типы ресурсов (меню, например) можно описать на специальном языке и воспользоватся при этом обычным текстовым редактором, поддерживающим текст в формате ASCII. Другие ресурсы (иконки, курсоры, изображения) тоже описываются в текстовом виде, но часть их описания является последовательность шестнадцатиричных цифр описывающих изображения. Можно, конечно, попробовать написать эту последовательностьи в текстовом редакторе, но, наверное, в этом случае сложность создания ресурса приблизится к сложности написания программы, а возможно, и превысит ее. Обычно для создания ресурсов пользуются специальными средствами - редакторами ресурсов. Они позволяют создавать ресурсы, визуально контролировать правильность их создания, после чего сохранять их в файлах ресурсов.
Я часто использую "смешанный" способ редактирования ресурсов. Например, при визуальном редактировании диалоговых окон достаточно трудно точно установить элементы диалогового окна именно так, как хочется. Устанавливаю все элементы ПРИБЛИЗИТЕЛЬНО на те места, где они должны находиться, после чего сохраняю ресурсы в виде файла с расширением RC. Затем редактирую RC-файл как обычный текстовый файл, точно указывая при этом все размеры и позиции.
При создании RC-файлов программист может столкнуться с одной тонкостью. Некоторые ресурсы, такие, как иконки, курсоры, диалоговые окна, изображения (bitmap'ы) могут быть сохранены в отдельных файлах с расширениями .ico, .cur, .dlg, .bmp соответственно. В этом случае в RC-файлах делаются ссылки на упомянутые файлы.
Файл ресурсов создан - теперь его нужно откомпилировать. Компилируется он специальным компилятором ресурсов. Обычно имя компилятора ресурсов заканчивается на RC.EXE. В частности, в Borland 5.0 он называется BRC.EXE.
После компиляции файла ресурсов компилятором ресурсов создается новый файл, имеющий расширение .RES. Именно этот RES-файл используется линкером для добавления ресурсов в bin-файл. Следует отметить, что при необходимости RES-файлы могут создаваться и редакторами ресурсов. В каком формате создавать ресурсы и как присоединять их к исполняемому файлу, зависит от потребностей и привычек создающего ресурсы программиста.
Итак, постараемся подвести итог сказанному. Ресурсы создаются и включаются в bin-файл посредством выполнения следующих шагов (некоторые шаги могут быть опущены в зависимости от обстоятельств)
Последовательность создания файла ресурсов
Действие | Используемое средство |
Создание RC-файла (при необходимости включающего ссылки на файлы с расширением
.ico, .cur, .bmp, .dlg, .mnu и т.д.)
|
Редактор ресурсов (при необходимости может быть использован текстовый редактор и графические редакторы) |
Редактирование RC-файла в текстовом виде | Текстовый редактор |
Компиляция RC-файла - получение RES-файла | Компилятор ресурсов |
Добавление ресурсов, содержащихся в RES-файле, в bin-файл | Линкер
|
Любой, кто хоть немного работал в Windows, знает, что меню располагаются сразу под заголовком окна и позволяют пользователю осуществить выбор возможностей, предоставляемых программой. Помимо этого, существуют также всплывающие меню, которые могут появлятся в любой точке экрана. Обычно их содержание зависит от того, на каком окне щелкнули клавишей мыши.
Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. В свою очередь, popup-меню должно указать на список структур очередного, более низкого уровня и т.д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его идентификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.
Эта многоуровневая древовидная структура описывается в файле ресурсов. Описание меню имеет вид:
MenuName MENU [параметры] ; это - главное меню { Описание всех popup-меню и элементов меню второго уровня }В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню.
В Win32 API для описания меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе - MENUITEM - описывает обычный элемент меню.
Всплывающие меню описывается следующим образом:
POPUP "Имя" [,параметры] ; описание popup-меню { Описание всех popup-меню и элементов очередного уровня }У конечного элемента меню в его описании есть еще одна характеристика - тот самый идентификатор действия:
MENUITEM "Имя", MenuID [,параметры]В обоих случаях "Имя" - это тот текст, который будет выведен на экран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOP (без кавычек!), на месте элемента меню появляется горизонтальная линия. Обычно эти горизонтальные линии (сепораторы или разделители) используются для разделения элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).
Если в имени меню встречается символ "&", то следующий за амперсандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одновременного нажатия клавиши Alt и подчеркнутого символа.
MenuID - идентификатор действия. Он может быть передан функции окна, содержащего меню. Значение идентификатора определяется пользователем. Функция окна в зависимости от полученного MenuID производит определенные действия.
Параметры же описывают способ появления элемента на экране. Возможные значения параметров приведены в таблице:
Параметры, описывающие элемент меню в файле ресурсов
Флаг | Значение |
CHECKED | Рядом с именем элемента может отображаться небольшой значек, говорящий о том, что соответствующий флаг установлен |
ENABLED | Элемент меню доступен |
DISABLED | Элемент меню недоступен, но отображается как обычный |
GRAYED | Элемент меню недоступен и отображается серым цветом |
MENUBREAK | Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце |
MENUBARBREAK | То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией |
Попробуем создать описание небольшого меню. Горизонтальное меню (menubar) позволит выбирать подменю "File", "Examples" и конечный элемент "Help". Подменю "File" будет содержать элементы "Open" и "Exit", разделенные горизонтальной линией, а подменю "Examples" - несколько конечных элементов.
Ниже приведен текст скрипта для этого меню:
MyMenu MENU { POPUP "&File" { MENUITEM "&Open", 101 MENUITEM SEPARATOR MENUITEM "E&xit", 102 } POPUP "&Examples" { POPUP "Example 1" { MENUITEM "1&1", 103 MENUITEM "1&2", 104 } POPUP "Example 2" { MENUITEM "2&1", 105 MENUITEM "2&2", 106 } } MENUITEM "&Help", 111 }
Основным средством "общения" пользователя с программой являются диалоговые окна (их также называют диалогами). В этом разделе мы рассмотрим работу диалоговых окон и их взаимодействие не только с пользователем и программой, но и окнами более низкого уровня, называемыми элементами управления, которые выполняют большую часть черновой работы, незаметной не только пользователям, но и программистам.
Диалоговые окна можно классифицировать по двум критериям. Первый критерий - это модальность
диалогового окна. Второй критерий, не всегда заметный, заслуживает особого внимания. Второй
критерий фактически определяет, с одной стороны, возможности диалогового окна, а с другой
стороны, ответственного за обработку сообщений посылаемых диалоговому окну. В подавляющем
большинстве случаев обработку сообщений, адресованных диалоговому окну, производит функция
диалогового окна (о ней речь впереди), но иногда диалоговое окно своей функции не имеет
(она запрятана в "глубинах" системы) и всю обработку производит система. Возможности таких
окон очень ограничены, в основном они предназначены для выдачи сообщений пользователю. Об
этом говорит даже их название - окна сообщений.
Диалоговые окна бывают модальными и немодальными.
Наиболее часто используются модальные окна. Эти окна не дают пользователю возможности работать
с другими окнами, созданными приложением, породившим диалоговое окно, но разрешают
переключаться на работу с другими приложениями. Для того чтобы пользователь мог продолжить
работу с другими окнами своего приложения, необходимо завершить работу с диалоговым окном.
В особых случаях, представляющих угрозу системе и требующих немедленной реакции оператора, могут использоваться системные модальные окна. Эти окна не позволяют переключаться ни на какое другое окно. Естественно, что и применять системное модальное окно нужно с умом.
Немодальные диалоговые окна не требуют своего завершения для продолжения работы, и
пользователь может во время работы с ними свободно переключаться на любое приложение.
Кнопки, списки и прочее
Элемент управления - это ОКНА более низкого по отношению к диалоговому окну уровня. Предлагаю отметить то, что элементы управления никогда не могут использоваться как самостоятельные окна. Они всегда используются на фоне какого-то окна, которое является для них родительским окном. Элементы управления, таким образом, всегда являются дочерними окнами, другими словами, у них всегда присутствует стиль WM_CHILD.
Как и любые другие окна, элементы управления могут получать и выдавать сообщения. Правда, это относится не ко всем элементам управления, но... Стоп! Давайте прервемся на секунду.
Мне бы хотелось обратить внимание читателя на один интересный момент. Для посылки сообщения обычно используют функции SendMessage() и SendDlgItemMessage(). Дело в том, что значение, которое возвращают эти функции, зависит только от того сообщения, которое они отправили. Таким образом, если вам необходимо узнать по возвращенному значению, что произошло в результате обработки того или иного сообщения, ищите описание возвращаемых значений не в описаниях функций, а описаниях сообщений.
По умолчанию подавляющее большинство сообщений от элементов управления получает диалоговое окно, которому они принадлежат. Диалоговое окно должно каким-то образом их обрабатывать. Отсюда очередной вывод - у диалогового окна должна быть собственная оконная функция.
Каждому элементу управления присваивается идентификатор. При каком-либо воздействии на этот орган управления со стороны пользователя диалоговое окно получает сообщение, содержащее идентификаторы элемента и типа производимого пользователем действия. Диалоговое функция обрабатывает эти сообщения и выполняет соответствующие действия. Этот процесс происходит параллельно с обработкой сообщений в оконной функции. При этом нужно заметить, что в отличие от обычного окна, "нормальное" диалоговое окно не имеет своего цикла обработки сообщений. Цикл обработки сообщений запускается один раз при запуске программы.
Элементами управления могут быть кнопки (buttons), которые мы уже использовали в окнах сообщений, переключатели (check boxes), селекторы (radio buttons), списки (list boxes), комбинированные списки (combo boxes), линейки проктутки (scroll bars) и статические элементы (statics). Все элементы в этом перечне относятся к категории базовых, и все они присутствовали и в Windows 3.x. На их основе Microsoft разработала серию новых элементов управления (common controls), которые позволили расширить возможности интерфейса с пользователем и улучшить внешний вид приложений. Мы рассмотрим как базовые, так и новые общие (как еще можно перевести на русский язык название "common controls"?) элементы управления.
Я уже упоминал, что основную часть работы диалогового окна выполняют элементы управления. Поэтому
рассмотрим сначала вопрос о том, как может быть создано диалоговое окно, а потом на примерах -
работу каждого типа элементов управления.
Диалоговое окно, как и меню, может быть создано несколькими способами: во-первых, с помощью описания его в файле ресурсов и, во-вторых, во время выполнения программы. Наиболее часто используется описание диалога в файле ресурсов. Лучше всего при создании диалого воспользоваться редактором ресурсов, с помощью которого может быть создан текстовый файл, содержащий описание диалогового окна. Ресурсы диалога в этом текстовом файле задаются оператором DIALOG, который имеет следующий формат:
DialogName DIALOG [DISCARDABLE] X,Y,Width,Height CAPTION "Заголовок окна" STYLE <Стили диалогового окна> FONT n, <имя шрифта> { Описание элементов диалога }В данном случае DialogName - это имя диалогового окна. Опция DISCARDABLE станет совершенно ясной при рассмотрении вопроса об организации памяти в Windows. Параметры X и Y - это координаты верхнего левого угла диалового окна, Width и Height - ширина и высота диалога. STYLE описывает стили окна. Здесь могут использоваться как стили, применяемые для описания обычных окон, так и стили, применяемые только в диалоговых окнах. Эти новые стили приведены в таблице:
Стили диалоговых окон
Стиль | Значение | Эффект |
DS_ABSALIGN | 0x0001L | Положение диалогового окна исчисляется в экранных координатах |
DS_SYSMODAL | 0x0002L | Создается системное модальное диалоговое окно |
DS_3DLOOK | 0x0004L | Создается диалоговое окно, имеющее зрительную иллюзию трехмерности |
DS_FIXEDSYS | 0x0008L | Вместо SYSTEM_FONT используется SYSTEM_FIXED_FONT |
DS_NOFAILCREATE | 0x0010L | Диалоговое окно создается, несмотря на то, что при его создании произошли ошибки |
DS_LOCALEDIT | 0x0020L | В 32-битных приложениях не используется |
DS_SETFONT | 0x0040L | Определяет шрифт, который будет применятся в диалоговом окне |
DS_MODALFRAME | 0x0080L | Создается модальное диалоговое окно |
DS_NOIDLEMSG | 0x0100L |
|
DS_SETFOREGROUND | 0x0200L | Поместить диалоговое окно на передний план |
DS_CONTROL | 0x0400L |
|
DS_CENTER | 0x0800L | Диалоговое окно помещается в центр рабочей области |
DS_CENTERMOUSE | 0x1000L |
|
DS_CONTEXTHELP | 0x2000L |
|
Приведенных выше сведений вполне достаточно, чтобы написать заготовку диалогового окна в файле ресурсов. Но какой смысл описывать диалоговое окно, если в нем нет ни одного из элементов управления? Ведь даже закрыть такое диалоговое окно (если в нем, конечно, нет системного меню) невозможно! Значит, нам необходимо срочно научиться описывать эти элементы!
Я уже упоминал о том, что в "недрах" Win32 есть масса предопределенных объектов. В частности, там находятся и некоторые предопределенные классы окон. К таким классам относятся кнопки (класс "button"), списки (класс "listbox"), комбинированные списки (класс "combobox"), окна редактирования (класс "edit"), полосы прокрутки (класс "scrollbar"), статистические элементы (класс "static"). У каждого класса есть свой определенный набор стилей, которые определяют внешний вид и поведение элементов управления, относящихся к данному классу.
Управление окном каждого класса, а также получение информации от него производится с помощью
обмена управляющими сообщениями. О действиях пользователей с ними элементы управления оповещают
свои родительские окна через нотификационные сообщения.
Перед тем, как начать рассказ о кнопках, хочу предостеречь читателя. Дело в том, что можно использовать кнопки и в обычных окнах. Но они, как и большинство элементов управления, проектировались для использования именно в диалоговых окнах. Использование кнопок в обычных окнах не рекомендуется, ибо это увеличивает риск того, что программа будет работать неправильно.
Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только PushButons (обычные нажимаемые кнопки), но и Check Boxes (обычно это небольшие квадратики, в которых можно установить или не установить пометку) и Radio Buttons (небольшие кружочки, В ОДНОМ из которых стоит точка). Пользователь может установить курсор мыши на кнопку, щелкнуть клавишей мыши - и кнопка пошлет диалоговому окну сообщение WM_COMMAND. То же произойдет и в случае, если пользователь сначала выберет кнопку клавишей Tab, а потом нажмет Enter.
В параметрах сообщения WM_COMMAND содержится информация, которой достаточно, чтобы диалоговое окно узнало, от какой кнопки пришло сообщение, какое действие требуется выполнить, и таким образом пользователь инициировал выдачу сообщения.
При этом необходимо отметить, что обычная кнопка (ее называют PushButton) не помнит того, что с ней делали, т.е. она на короткое время становится нажатой, а затем возвращается в исходное состояние. Можно нажать кнопку десять раз подряд, и все десять раз она пошлет диалоговому окну одно и то же сообщение, если, конечно, кнопка не сделана запрещенной. CheckBox помнит о том, что он находится в одном из двух состояний - установленом или не установленном, некоторые CheckBox'ы могут находиться еще и в неопределенном состоянии.
Говорит о состоянии одной RadioButton бессмысленно. Дело в том, что RadioButton'ы предназначены для осуществления выбора одного из нескольких взаимоисключаемых вариантов, поэтому можно говорить о группе (иногда говорят кластере) RadioButton'ов. В частности, для объединения RadioButton'ов в кластеры служит такой элемент управления, как группа (GroupBox). Обычно группы используются для группирования органов управления только для улучшения дизайна диалоговых окон. Что же касается RadioButton'ов, то без обрамляющей группы существование кластера не имеет смысла.
Формат описания кнопок в окне диалога достаточно прост:
CONTROL "Заголовок", ButtonID, class, styles, X, Y, Width, HeightX, Y, Width, Height - это все ясно. Все то же самое, что и при описании непосредственно диалогового окна. "Заголовок" - надпись на кнопке или рядом с кнопкой. ButtonID - идентификатор кнопки, т.е. значение, которое посылается диалоговому окну при нажатии кнопки в качестве LOWORD(wParam). Через HIWORD(wParam) диалоговое окно получает код нотификации, т.е. код того действия, которое произвел пользователь. Примерами действия пользователя могут служить нажатие клавиши Enter, двойной щелчек правой или левой клавишей мыши и так далее. А источник, т.е. хэндл инициировавшего сообщение окна, сообщения содержится в lParam (я напомню, что если сообщение приходит от меню, то lParam всегда равен 0, а если от акселератора - 1). Все легко и просто. Сложности начинаются при рассмотрении класса, определяющегося полем class, типа кнопки и стиля кнопки, которые определяются параметром style.
Для кнопок, вне зависимости от того, PushButton ли это, RadioButton или CheckBox, класс всегда определяется как "button".
Читатель, наверное, уже привык к тому, что ответы на большинство вопросов можно найти в файлах заголовках и файлах помощи Win32. Поэтому и сейчас, как всегда, смотрим в заголовочный файл winuser.h, выбираем оттуда стили кнопок, которые начинаются с букв BS_, и сводим их в таблицу. Надоело изучать эти таблицы? Ничего, тяжело в учении - легко в бою! © А.В. Суворов, "Наука побеждать".
Стили кнопок
Флаг | Значение | Эффект |
BS_PUSHBUTTON | 0x00000000L | Создается обычная кнопка |
BS_DEFPUSHBUTTON | 0x00000001L | Создается обычная кнопка, которая срабатывает при нажатии "Enter" даже тогда, когда не выбрана |
BS_CHECKBOX | 0x00000002L | Создается CheckBox, при нажатии состояние автоматически не изменяется, забота об этом ложится на программу |
BS_AUTOCHECKBOX | 0x00000003L | Создается CheckBox, который автоматически меняет свое состояние при нажатии |
BS_RADIOBUTTON | 0x00000004L | Создается Radio Button, автоматически состояние не меняется |
BS_3STATE | 0x00000005L | То же, что и BS_CHECKBOX, но имеет три состояния - включенное, отключенное и неопределенное, автоматически состояние не меняет |
BS_AUTO3STATE | 0x00000006L | То же, что и предыдущее, но состояние меняется автоматически |
BS_GROUPBOX | 0x00000007L | Группа |
BS_USERBUTTON | 0x00000008L | Устаревший стиль, необходимо использовать BS_OWNERDRAW |
BS_AUTORADIOBUTTON | 0x00000009L | То же, что и RadioButton, но при нажатии состояние меняется автоматически |
BS_OWNERDRAW | 0x0000000BL | За прорисовку кнопки отвечает программа, а не система |
BS_LEFTTEXT | 0x00000020L | Текст помещается слева от RadioButton'а или CheckBox'а, то же, что и BS_RIGHTBUTTON |
BS_TEXT | 0x00000000L | Внутри или рядом с кнопкой отображается текст |
BS_ICON | 0x00000040L | Внутри кнопки или рядом с кнопкой отображается иконка |
BS_BITMAP | 0x00000080L | Внутри кнопки или рядом с кнопкой отображается bitmap |
BS_LEFT | 0x00000100L | Размещает текст у левого края прямоугольника, выделенного для размещения кнопки |
BS_RIGHT | 0x00000200L | Размещает текст у правого края прямоугольника, выделенного для размещения кнопки |
BS_CENTER | 0x00000300L | Размещает текст по горизонтали в центре прямоугольника, выделенного для размещения кнопки |
BS_TOP | 0x00000400L | Размещает текст у верхнего края прямоугольника, выделенного для размещения кнопки |
BS_BOTTOM | 0x00000800L | Размещает текст у нижнего края прямоугольника, выделенного для размещения кнопки |
BS_VCENTER | 0x00000C00L | Размещает текст по вертикали в центре прямоугольника, выделенного для размещения кнопки |
BS_PUSHLIKE | 0x00001000L | Делает CheckBox или RadioButton внешне похожими на PushButton |
BS_MULTILINE | 0x00002000L | При необходимости текст разбивается на несколько строк |
BS_NOTIFY | 0x00003000L | Разрешает посылку родительскому окну нотификационных сообщений BN_DBLCLK, BN_KILLFOCUS и BN_SETFOCUS |
BS_FLAT | 0x00008000L | Не добавляется имитация трехмерности изображения элемента управления |
BS_RIGHTBUTTON | 0x00000020L | RadioButton или CheckBox размещаются справа от надписи (то же, что и BS_LEFTTEXT) |
Для использования собственной иконки нужно создать файл ресурса:
-->Begin icon.rc <-- ICON_1 ICON 180.ico ICON_SM ICON 163.ico -->End of icon.rc<--
Затем icon.rc откомпилировать с помощью какого-нибудь компилятора ресурсов(res-файл должен
быть 32-bit Windows compatible). Возьмем brcc32.exe:
brcc32 icon.rcПолучим icon.res - понадобится при линковании.
WNDCLASSEX struct clSize dword ? clStyle dword ? clLpfnWndProc dword ? clCbClsExtra dword ? clCbWndExtra dword ? clHInstance dword ? clHIcon dword ? clHCursor dword ? clHbrBackground dword ? clLpszMenuName dword ? clLpszClassName dword ? clHIconSm dword ? WNDCLASSEX ends
clHIcon: Содержит дискриптор иконки получаемый после вызова LoadIcon.
clHIconSm: Тоже самое для маленькой иконки, если null то используется hIcon.
-------------------------------------------------- .data szIconName db 'ICON_1', 0 szIconSmName db 'ICON_SM', 0 ... -------------------------------------------------- .code ... push offset szIconName push [hInst] call LoadIconA mov [wc.clHIcon], eax push offset szIconSmName push [hInst] call LoadIconA mov [wc.clHIconSm], eax ... регистируем класс, создаем окно... ---------------------------------------------------
При линковании нужно указать файл ресурса:
tlink32 [options] my.obj,my.exe,,,,my.res
Общий вид ресурса меню:
MyMenu MENU { [menu list] }или
MyMenu MENU BEGIN [menu list] ENDПоле [menu list] может быть MENUITEM или POPUP. Синтаксис MENUITEM:
MENUITEM "&text", ID [,options]&text - название пункта меню, амперсанд ставится перед подчеркиваемым символом.
ID - уникальный идентификатор, будет передоваться процедуре окна(wparam), если этот пункт меню выберут.
Параметры options:
GRAYED - Недоступный пункт меню, текст выводится серым цветом.
INACTIVE - Тоже недоступный пункт меню, но текст нормальный.
MENUBREAK - Этот пункт и следующие пункты появляются в новой строке меню.
HELP - Пункт меню и следующие пункты выводятся в правой части строки меню.
Синтаксис POPUP:
POPUP "&text" [,options] { [menu list] }MENUITEM SEPARATOR - разделитель для отделения элементов меню.
Далее при регистрации класса в поле clsLpszMenuName указать на строку с названием ресурса меню.
Пример:
-- Begin menu.rc -- #define IDM_1 1 #define IDM_2 2 #define IDM_EXIT 3 #define IDM_ABOUT 4 MENU_1 MENU BEGIN POPUP "File" BEGIN MENUITEM "MenuItem &GRAYED", IDM_1, GRAYED MENUITEM "MenuItem &INACTIVE", IDM_2, INACTIVE MENUITEM SEPARATOR MENUITEM "&Exit", IDM_EXIT END MENUITEM "&About", IDM_ABOUT END -- End of menu.rc -- -- Begin cr_menu1.asm -- .386 .model flat, stdcall include win32.inc extrn CreateWindowExA:PROC extrn DefWindowProcA:PROC extrn DispatchMessageA:PROC extrn ExitProcess:PROC extrn GetMessageA:PROC extrn GetModuleHandleA:PROC extrn LoadCursorA:PROC extrn LoadIconA:PROC extrn MessageBoxA:PROC extrn PostQuitMessage:PROC extrn RegisterClassA:PROC extrn ShowWindow:PROC extrn TranslateMessage:PROC extrn UpdateWindow:PROC IDM_1 EQU 1 IDM_2 EQU 2 IDM_EXIT EQU 3 IDM_ABOUT EQU 4 .data newhwnd dd 0 msg MSGSTRUCT wc WNDCLASS hInst dd 0 szMenuName db 'MENU_1', 0 <<название ресурса>> szTitleName db 'Win32 Assembly Program', 0 szClassName db 'ASMCLASS32',0 mb_message db 'Win32 Assembler: Menu',0 mb_title db 'About',0 .code ;---------------------------------------------------------------------- start: push 0 call GetModuleHandleA ; get hmod (in eax) mov [hInst], eax ; hInstance is same as HMODULE ; in the Win32 world ; initialize the WndClass structure mov [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS mov [wc.clsLpfnWndProc], offset WndProc mov [wc.clsCbClsExtra], 0 mov [wc.clsCbWndExtra], 0 mov eax, [hInst] mov [wc.clsHInstance], eax push IDI_APPLICATION push 0 call LoadIconA mov [wc.clsHIcon], eax push IDC_ARROW push 0 call LoadCursorA mov [wc.clsHCursor], eax mov [wc.clsHbrBackground], COLOR_WINDOW + 1 mov dword ptr [wc.clsLpszMenuName], offset szMenuName <<указываем смещение на строку с названием ресурса>> mov dword ptr [wc.clsLpszClassName], offset szClassName push offset wc call RegisterClassA push 0 ; lpParam push [hInst] ; hInstance push 0 ; menu push 0 ; parent hwnd push CW_USEDEFAULT ; height push CW_USEDEFAULT ; width push CW_USEDEFAULT ; y push CW_USEDEFAULT ; x push WS_OVERLAPPEDWINDOW ; Style push offset szTitleName ; Title string push offset szClassName ; Class name push 0 ; extra style call CreateWindowExA mov [newhwnd], eax push SW_SHOWNORMAL push [newhwnd] call ShowWindow push [newhwnd] call UpdateWindow msg_loop: push 0 push 0 push 0 push offset msg call GetMessageA cmp ax, 0 je end_loop push offset msg call TranslateMessage push offset msg call DispatchMessageA jmp msg_loop end_loop: push [msg.msWPARAM] call ExitProcess ;-------------------------------------------------------------- WndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD cmp [wmsg], WM_DESTROY je wmdestroy cmp [wmsg], WM_COMMAND je wmcommand push [lparam] push [wparam] push [wmsg] push [hwnd] call DefWindowProcA jmp finish wmdestroy: push 0 call PostQuitMessage mov eax, 0 jmp finish wmcommand: mov eax, [wparam] cmp eax, IDM_ABOUT jne wmnext push 0 push offset mb_title push offset mb_message push 0 call MessageBoxA wmnext: cmp eax, IDM_EXIT je wmdestroy finish: ret WndProc endp ends end start -- End of cr_menu1.asm --
Меню можно добавить в процессе создания окна, использовав при этом аргумент hMenu - дискриптор меню. Его возращает функция LoadMenu.
==>Begin cr_menu2.asm<== .386 .model flat, stdcall include win32.inc extrn CreateWindowExA:PROC extrn DefWindowProcA:PROC extrn DispatchMessageA:PROC extrn ExitProcess:PROC extrn GetMessageA:PROC extrn GetModuleHandleA:PROC extrn LoadCursorA:PROC extrn LoadIconA:PROC extrn LoadMenuA:PROC extrn MessageBoxA:PROC extrn PostQuitMessage:PROC extrn RegisterClassA:PROC extrn ShowWindow:PROC extrn TranslateMessage:PROC extrn UpdateWindow:PROC IDM_1 EQU 1 IDM_2 EQU 2 IDM_EXIT EQU 3 IDM_ABOUT EQU 4 .data newhwnd dd 0 hMenu dd ? msg MSGSTRUCT wc WNDCLASS hInst dd 0 szMenuName db 'MENU_1', 0 szTitleName db 'Win32 Assembly Program', 0 szClassName db 'ASMCLASS32',0 mb_message db 'Win32 Assembler: Menu',0 mb_title db 'About',0 .code ;----------------------------------------------------------------------------- start: push 0 call GetModuleHandleA ; get hmod (in eax) mov [hInst], eax ; hInstance is same as HMODULE ; in the Win32 world ; initialize the WndClass structure mov [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS mov [wc.clsLpfnWndProc], offset WndProc mov [wc.clsCbClsExtra], 0 mov [wc.clsCbWndExtra], 0 mov eax, [hInst] mov [wc.clsHInstance], eax push IDI_APPLICATION push 0 call LoadIconA mov [wc.clsHIcon], eax push IDC_ARROW push 0 call LoadCursorA mov [wc.clsHCursor], eax push offset szMenuName push [hInst] call LoadMenuA mov [hMenu], eax mov [wc.clsHbrBackground], COLOR_WINDOW + 1 mov dword ptr [wc.clsLpszMenuName], 0 mov dword ptr [wc.clsLpszClassName], offset szClassName push offset wc call RegisterClassA push 0 ; lpParam push [hInst] ; hInstance push [hMenu] ; menu push 0 ; parent hwnd push CW_USEDEFAULT ; height push CW_USEDEFAULT ; width push CW_USEDEFAULT ; y push CW_USEDEFAULT ; x push WS_OVERLAPPEDWINDOW ; Style push offset szTitleName ; Title string push offset szClassName ; Class name push 0 ; extra style call CreateWindowExA mov [newhwnd], eax push SW_SHOWNORMAL push [newhwnd] call ShowWindow push [newhwnd] call UpdateWindow msg_loop: push 0 push 0 push 0 push offset msg call GetMessageA cmp ax, 0 je end_loop push offset msg call TranslateMessage push offset msg call DispatchMessageA jmp msg_loop end_loop: push [msg.msWPARAM] call ExitProcess ;----------------------------------------------------------------------------- WndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD cmp [wmsg], WM_DESTROY je wmdestroy cmp [wmsg], WM_COMMAND je wmcommand push [lparam] push [wparam] push [wmsg] push [hwnd] call DefWindowProcA jmp finish wmdestroy: push 0 call PostQuitMessage mov eax, 0 jmp finish wmcommand: mov eax, [wparam] cmp eax, IDM_ABOUT jne wmnext push 0 push offset mb_title push offset mb_message push 0 call MessageBoxA wmnext: cmp eax, IDM_EXIT je wmdestroy finish: ret WndProc endp ends end start ==>End of cr_menu2.asm<==
Меню можно создать динамически, без создания ресурса.
Программа cr_menu3.asm имеет в памяти шаблон меню, потом из шаблона создаст "объект" меню с
помощью LoadMenuIndirect, которая вернет дискриптор меню. Меню может затем быть добавлено при
создании окна, устанавливая дискриптор меню в соответствующий аргумент CreateWindowEx.
Вы можете также создать окно без меню и потом дополнить используя SetMenu.
Эта функция API может также использоваться для изменения меню. Меню могут удалить
используя NULL вместо дискриптора меню.
push offset MenuTemplate ;шаблон меню call LoadMenuIndirectA mov [hMenu], eax ;hMenu - menu handleШаблон меню состоит из заголовка и списка пунктов меню.
MENUEX_TEMPLATE_HEADER struct wVersion dw ? wOffset dw ? dwHelpId dd ? MENUEX_TEMPLATE_HEADER endswVersion - версия шаблона, должна быть равна 1.
wOffset - смещение относительно конца этого поля. Если первое определение пункта меню следует за dwHelpId, то wOffset должен быть 4.
dwHelpId - идентификатор подсказки.
Заголовок должен выравниваться по DWORD границе.
Пункт меню:
typedef struct { DWORD dwType; DWORD dwState; UINT uId; WORD bResInfo; WCHAR szText[1]; // DWORD dwHelpId; } MENUEX_TEMPLATE_ITEM;dwType - тип пункта меню.
dwState - состояние пункта меню.
UINT - уникальный идентификатор.
bResInfo - величина введенная для обозначения последнего пункта меню, подменю...
szText - текст пункта меню. Это Unicode строка оканчивающаяся 0, выравнивается по WORD границе.
dwHelpId - идентификатор подсказки. Нужен только при создании POPUP меню.
Пример:
MenuTemplate MENUEX_TEMPLATE_HEADER<1,4,0> ; &File dd MFT_STRING ; Type dd MFS_ENABLED ; State dd 0 ; Id dw MFR_POPUP ; ResInfo dw '&','F','i','l','e',0,0 ; Text dd 0 ; HelpId ; MenuItem &GRAYED dd MFT_STRING ; Type dd MFS_GRAYED ; State dd IDM_1 ; Id dw 0 ; ResInfo dw 'M','e','n','u','I','t','e','m',' ' ; Text dw '&','G','R','A','Y','E','D',0 ; dd 0 - HelpId only with popup menu ; MenuItem &DISABLED dd MFT_STRING ; Type dd MF_DISABLED ; State dd IDM_2 ; Id dw 0 ; ResInfo dw 'M','e','n','u','I','t','e','m',' ' ; Text dw '&','D','I','S','A','B','L','E','D',0 ; dd 0 ; HelpId ; ------------------ dd MFT_SEPARATOR,0,0 dw 0,0 ; E&xit dd MFT_STRING ; Type dd MFS_ENABLED ; State dd IDM_EXIT ; Id dw MFR_END ; ResInfo dw 'E','&','x','i','t',0,0 ; Text ; dd 0 ; HelpId ; &About dd MFT_STRING ; Type dd MFS_ENABLED ; State dd IDM_ABOUT ; Id dw MFR_END ; ResInfo dw '&','A','b','o','u','t',0 ; Text ; dd 0 ; HelpId
Установить новое меню можно с помощью функции SetMenu, указав ей дескриптор нового меню.
Вот пример:
push offset szMenuName2 ;новое меню push [hInst] call LoadMenuA ;получаем hMenu ;eax - hMenu push eax push [hwnd] ; handle текущего окна call SetMenu ; устанавливаем менюДля удаления меню нужно использовать DestroyMenu. В отличие от SetMenu с hMenu=NULL, DestroyMenu освобождает память.
push [hwnd] call GetMenu ; получим hMenu ;eax - hMenu push eax call DestroyMenu push [hwnd] call DrawMenuBar ;перерисуем меню, точнее очистим