В этой главе мы выполним классификацию типов меню , научим вас создавать, изменять и удалять меню в приложениях Windows.
Вы знаете, что меню используются в приложениях Windows для выбора отдельных команд или изменения режимов работы приложений. Программный интерфейс Windows обеспечивает сильную поддержку меню, так как меню - важный элемент пользовательского интерфейса.
Создавая меню в программах MS-DOS, вы были вынуждены либо приобретать специальные библиотеки функций или другие средства (C Tools, Turbo Vision, Vitamin C и т. п.), либо создавать свои функции для работы с меню.
Для того чтобы создать меню в приложении Windows, вам достаточно разработать его внешний вид и создать шаблон при помощи редактора Resource Workshop или аналогичного средства графического проектирования элементов пользовательского интерфейса. Шаблон меню следует записать в ресурсы приложения, после чего за работу меню отвечает операционная система Windows. Когда вы выбираете строку из меню, ваше приложение получает сообщение WM_COMMAND. Это сообщение содержит идентификатор выбранной строки.
Таким образом, при создании приложения, работающего с меню, задача программиста сводится к определению обработчика сообщения WM_COMMAND, поступающего от меню. Вы можете выбирать из меню при помощи мыши или клавиатуры, при этом сам процесс выбора (т. е. выделение строк меню, показ временных меню и т. п.) обеспечивается операционной системой Windows. Ваше приложение получает сообщение о том, что сделан выбор той или иной строки из меню, но для обеспечения работы приложения программисту нет необходимости знать, каким именно способом был сделан выбор.
Прежде чем приступить к описанию средств операционной системы Windows, предназначенных для работы с меню, мы расскажем о том, какие стандартные типы меню можно создать в приложениях Windows. Вы также можете создать свои собственные, нестандартные типы меню, работающие так, как это должно быть с вашей точки зрения. Однако нестандартные элементы диалогового интерфейса могут затруднить работу пользователя с приложением и вступить в противоречие с концепцией стандартного пользовательского интерфейса.
При создании окна в приложении Windows вы можете указать, что окно должно иметь меню. Обычно меню создается в главном окне приложения. Такое меню мы будем называть меню приложения .
На рис. 1.1 показано главное окно стандартного приложения Paintbrush, имеющее меню.
Рис. 1.1. Меню приложения Paintbrush
На этом рисунке меню приложения Paintbrush располагается ниже заголовка окна. Меню содержит отдельные элементы, или строки ("File", "Edit", "View", и т. д.), расположенные в полосе меню (menu bar ).
Для активизации строки меню вам надо установить на нее курсор и сделать щелчок левой клавишей мыши, либо нажать клавишу <Alt> и затем клавишу, соответствующую подчеркнутой букве. Например, для активизации строки "File" следует использовать клавиши <Alt> и <F>. Если нажать, а затем отпустить клавишу <Alt>, нужную строку в меню приложения можно будет выбрать клавишами перемещения курсора по горизонтали <Left> и <Right>. Для активизации строки в последнем случае после выбора следует нажать клавишу <Enter>. Для отказа от выбора можно воспользоваться клавишей <Esc>.
Строки меню могут быть использованы либо для выбора команд, либо для активизации дополнительных временных меню (pop-up menu ). Как правило, строки меню приложения используются только для активизации временных меню, но не для выполнения команд. Некоторые строки меню могут отображаться серым цветом. Это заблокированные строки, которые не могут быть выбраны.
Временное меню (рис. 1.2) появляется на экране после выбора строки в меню приложения.
Рис. 1.2. Временное меню
Временное меню содержит строки, расположенные в столбец. Для выбора строки из временного меню вы можете воспользоваться мышью или клавишами перемещения курсора по вертикали <Up> и <Down>. В последнем случае для завершения выбора следует нажать клавишу <Enter>. Можно также воспользоваться клавишей <Alt> и клавишей, соответствующей подчеркнутой букве в нужной строке.
Строки временного меню могут быть отмечены галочкой (рис. 1.2). Такие строки обычно используются как переключатели, изменяющие режим работы приложения. Например, если в меню "View" приложения Paintbrush выбрать строку "Cursor Position", слева от строки будет нарисована галочка, а в окне Paintbrush вы увидите текущие координаты курсора мыши. Если выбрать эту же строку еще раз, галочка пропадет. Режим отображения координат курсора будет выключен.
Мы уже говорили, что режимы работы приложения обычно задаются при помощи диалоговых панелей. Однако в простых случаях можно воспользоваться и строками меню.
Если выбор строки меню приводит к выполнению команды (например, команды создания документа, завершения работы приложения, копирования фрагмента документа в универсальный буфер обмена Clipboard и т. д.), строка меню содержит краткое название выполняемой команды, например, "New", "Copy", и т. д. Если же при выборе строки на экране появляется диалоговая панель, к слову справа добавляется многоточие (рис. 1.3). Последнее соглашение не является обязательным, однако вы должны ему следовать для обеспечения стандартного пользовательского интерфейса.
Рис. 1.3. Меню "File" приложения Paintbrush
Вы можете создавать многоуровневые меню . На рис. 1.4. мы привели внешний вид многоуровневого меню из приложения Borland Turbo C++ for Windows версии 3.1. Если из меню "Options" выбрать строку, отмеченную символом "", на экране появится меню второго уровня.
Рис. 1.4. Многоуровневое меню
Можно использовать многократную вложенность меню. Однако мы не советуем вам увлекаться сложными многоуровневыми меню, так как ими трудно пользоваться. В частности, систему настройки параметров Borland Turbo C++ for Windows версии 3.1 трудно назвать удобной в использовании. В Borland C++ for Windows версии 4.01 используется более удобный способ, основанный на применении диалоговых панелей (рис. 1.5).
Рис. 1.5. Диалоговая панель настройки параметров в Borland C++ for Windows версии 4.01
При помощи динамически изменяемого списка "Topics" вам предоставляется возможность выбора нужной группы параметров. В правой части диалоговой панели отображаются переключатели и другие органы управления, соответствующей выбранной группе параметров. Если вы выберете другую группу параметров, вид правой части диалоговой панели изменится соответствующим образом. Такая система настройки параметров намного удобнее многоуровневых меню, использованных в предыдущих версиях Borland C++.
Каждое стандартное приложение Windows имеет системное меню , которое можно вызвать щелчком левой клавиши мыши по окну активизации системного меню, расположенному слева от заголовка окна либо при помощи комбинации клавиши <Alt> и клавиши пробела (рис. 1.6).
Рис. 1.6. Системное меню приложения Paintbrush
Как правило, системное меню любого стандартного приложения содержит строки, показанные на рис. 1.6. С помощью системного меню вы можете минимизировать (строка "Minimize") или максимизировать ("Maximize") главное окно приложения, восстанавливать размер этого окна ("Restore"), перемещать окно ("Move") или изменять его размер ("Size"), закрывать окно ("Close") и переключаться на другие приложения ("Switch To..."). Ваше приложение может изменять системное меню, дополняя его новыми строками или горизонтальными разделительными линиями, удалять строки из существующего меню.
Приложение может создать меню в любом месте экрана. На рис. 1.7 показано меню внутри окна редактирования текста, созданное в среде разработки программ Borland C++ for Windows версии 4.01 щелчком правой клавиши мыши.
Рис. 1.7. Меню внутри окна приложения Borland C++ for Windows версии 4.01
Такое меню называют плавающим (floating menu ), подчеркивая тот факт, что меню может появится в любом месте экрана или окна приложения.
В некоторых случаях плавающие меню удобнее обычных. Вы можете создавать плавающее меню двойным щелчком левой клавиши мыши или щелчком правой клавиши мыши, а также любым другим аналогичным способом. Если плавающее меню появится вблизи курсора мыши, из него будет легче выбрать нужную строку, чем из обычного меню, так как не надо перемещать курсор в верхнюю часть экрана. Кроме того, создавая плавающее меню щелчком мыши, вы можете изменять внешний вид этого меню в зависимости от объекта, по изображению которого был сделан щелчок.
Это позволит реализовать объектно-ориентированный подход в работе пользователя с приложением - в зависимости от того, для какого объекта было создано плавающее меню, изменяется содержимое меню. Таким образом, для того чтобы выполнить какую-либо операцию над объектом, пользователю будет достаточно щелкнуть по нему мышью. Около объекта появится плавающее меню операций, определенных для данного объекта.
Меню не обязательно должно содержать только текстовые строки. Вы можете создать меню из графических изображений или из комбинации графических изображений и текста. На рис. 1.8 показано меню приложения MENU из примеров приложений, поставляющихся вместе с системой разработки приложений Microsoft SDK для Windows версии 3.1.
Рис. 1.8. Меню приложения MENU, содержащее графические изображения
Для создания меню с графическими изображениями можно использовать методы, аналогичные применяемым при создании органов управления, рисуемых родительским окном, или специальные функции из программного интерфейса Windows.
Для создания меню вы можете использовать три метода.
Во-первых, можно описать шаблон меню в файле ресурсов приложения (аналогично шаблону диалоговой панели, но с использованием других операторов). Этот способ больше всего подходит для создания статических меню, не меняющихся или меняющихся не очень сильно в процессе работы приложения.
Во-вторых, можно создать меню "с нуля" при помощи специальных функций программного интерфейса Windows. Этот способ хорош для приложений, меняющих внешний вид меню, когда вы не можете создать заранее подходящий шаблон. Разумеется, второй метод пригоден и для создания статических меню.
В-третьих, можно подготовить шаблон меню непосредственно в оперативной памяти и создать меню на базе этого шаблона.
Шаблон меню можно создать в текстовом виде либо при помощи приложения Resource Workshop, либо обычным текстовым редактором, например, входящим в состав Borland Turbo C++ for Windows. В любом случае перед сборкой приложения текстовое описание шаблона меню должно находиться в файле ресурсов с расширением имени .rc, указанном в проекте приложения (или в файле, включаемом в файл проекта оператором #include).
Описание шаблона меню имеет следующий вид:
nameID MENU [load] [mem] BEGIN . . . . . . . . . END
Поле nameID используется для идентификации шаблона меню. Оно может указываться либо в виде текстовой строки, либо в виде числа от 1 до 65535.
Параметр load - необязательный. Он используется для определения момента загрузки меню в память. Если этот параметр указан как PRELOAD, меню загружается в память сразу после запуска приложения. По умолчанию используется значение LOADONCALL, в этом случае загрузка шаблона в память происходит только при отображении меню.
Параметр mem также необязательный. Он влияет на тип памяти, выделяемой для хранения шаблона, и может указываться как FIXED (ресурс всегда остается в фиксированной области памяти), MOVEABLE (при необходимости ресурс может перемещаться в памяти, это значение используется по умолчанию) или DISCARDABLE (если ресурс больше не нужен, занимаемая им память может быть использована для других задач). Значение DISCARDABLE может использоваться вместе со значением MOVEABLE.
Между строками BEGIN и END в описании шаблона располагаются операторы описания строк MENUITEM и операторы описания временных меню POPUP.
Оператор MENUITEM имеет следующий формат:
MENUITEM text, id [, param]
Параметр text определяет имя строки меню. Вы должны указать текстовую строку в двойных кавычках, например, "File". Текстовая строка может содержать символы &, \t, \a.
Если в текстовой строке перед буквой стоит знак &, при выводе меню данная буква будет подчеркнута. Например, строка "&File" будет отображаться как "File". Клавиша, соответствующая подчеркнутой букве, может быть использована в комбинации с клавишей <Alt> для ускоренного выбора строки. Для того чтобы записать в строку сам символ &, его следует повторить дважды. Аналогично, для записи в строку меню символа двойной кавычки " его также следует повторить дважды.
Символ \t включает в строку меню символ табуляции и может быть использован при выравнивании текста в таблицах. Этот символ обычно используется только во временных и плавающих меню, но не в основном меню приложения, расположенном под заголовком главного окна.
Символ \a выравнивает текст по правой границе временного меню или полосы меню.
Параметр id представляет собой целое число, которое должно однозначно идентифицировать строку меню. Приложение получит это число в параметре wParam сообщения WM_COMMAND, когда вы выберете данную строку.
Необязательный параметр param указывается как совокупность атрибутов, разделенных запятой или пробелом. Эти атрибуты определяют внешний вид и поведение строки меню:
Атрибут |
Описание |
CHECKED |
При выводе меню на экран строка меню
отмечается галочкой |
GRAYED |
Строка меню отображается серым цветом и
находится в неактивном состоянии. Такую строку
нельзя выбрать. Этот атрибут несовместим с
атрибутом INACTIVE |
HELP |
Слева от текста располагается
разделитель в виде вертикальной линии |
INACTIVE |
Строка меню отображается в нормальном
виде (не серым цветом), но находится в неактивном
состоянии. Этот атрибут несовместим с атрибутом
GRAYED |
MENUBREAK |
Если описывается меню верхнего уровня,
элемент меню выводится с новой строки. Если
описывается временное меню, элемент меню
выводится в новом столбце |
MENUBARBREAK |
Аналогично атрибуту MENUBREAK, но
дополнительно новый столбец отделяется
вертикальной линией (используется при создании
временных меню) |
Для описания временных меню используется оператор POPUP :
POPUP text [, param] BEGIN . . . . . . . . . END
Между строками BEGIN и END в описании временного меню располагаются операторы описания строк MENUITEM и операторы описания вложенных временных меню POPUP.
Параметры text и param указываются так же, как и для оператора MENUITEM .
Для того чтобы создать в меню горизонтальную разделительную линию, используется специальный вид оператора MENUITEM :
MENUITEM SEPARATOR
Поясним сказанное выше на простом примере.
Скоро мы приведем исходные тексты приложения, имеющего меню (рис. 1.9). Это приложение называется, конечно, MENU.
Рис. 1.9. Главное окно приложения MENU, имеющего меню
Меню этого приложения состоит из строк "File", "Edit" и "Help". При выборе любой строки на экране появляется одно из трех временных меню.
Меню "File" (рис. 1.10) содержит строки, предназначенные для выполнения стандартных для приложений Windows команд, таких, как создание нового документа (или другого объекта) "New", загрузка документа "Open...", и т. д. Обратите внимание, что после строк "Save as..." и "Printer setup..." располагаются горизонтальные разделительные линии.
Рис. 1.10. Меню "File"
На рис. 1.11 показано временное меню "Edit". Оно организовано в виде таблицы из двух столбцов. В левом столбце находятся названия команд ("Undo", "Cut", "Copy", "Paste"), в правом - обозначения комбинаций клавиш, которые предназначены для ускоренного выбора строки меню.
Рис. 1.11. Меню "Edit"
Временное меню "Help" (рис. 1.12) содержит две неактивные строки ("Index" и "Keyboard"), три неактивные строки, отображаемые серым цветом ("Commands", "Procedures", "Using Help"), горизонтальную разделительную линию и обычную строку "About...").
Рис. 1.12. Меню "Help"
Для того чтобы создать описанное выше меню, в приложении Menu Demo в файле ресурсов определен шаблон меню:
#include "menu.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM "&Open...", CM_FILEOPEN MENUITEM "&Save", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE END POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, INACTIVE MENUITEM "&Keyboard", CM_HELPKEYBOARD, INACTIVE MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
Шаблон меню начинается с оператора MENU, в котором определено меню с именем APP_MENU. Это меню состоит из трех временных меню, описанных оператором POPUP.
Для определения строк временных меню используется оператор MENUITEM. В качестве второго оператора используются константы, символическое имя которых имеет префикс CM_. Мы определили эти константы в файле menu.hpp, включаемом в файл описания ресурсов оператором #include (можно использовать любые целые неодинаковые значения):
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341 #define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321 #define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331
Обратите внимание на определение временного меню "Edit". Для того чтобы организовать меню в виде таблицы из двух столбцов, мы использовали символ табуляции \t.
В описании временного меню "Help" используются атрибуты строк INACTIVE и GRAYED. Напомним, что строки с такими атрибутами являются неактивными, их невозможно выбрать. Разница между этими атрибутами заключается в том, что строка с атрибутом INACTIVE выводится нормальным цветом (как активная строка меню), а строка с атрибутом GRAYED выводится серым цветом.
Вы сможете легко подготовить описание шаблона меню при помощи текстового редактора в файле описания ресурсов, однако удобнее воспользоваться редактором ресурсов Resource Workshop. С помощью этого редактора вы можете создать шаблон меню и выполнить его тестирование, получив в результате текстовое описание шаблона, которое впоследствии можно редактировать. Именно так мы и поступили, создавая описанное выше меню.
Опишем кратко процесс создания шаблона меню при помощи приложения Resource Workshop.
Для того чтобы создать шаблон меню редактором ресурсов Resource Workshop, запустите его и из меню "File" выберите строку "New project...". В появившейся на экране диалоговой панели выберите тип ресурса - файл .RC, вслед за чем нажмите на кнопку "OK". Если файл описания ресурсов проектируемого приложения уже существует, вы можете открыть его, выбрав из меню "File" строку "Open project...".
Далее из меню "Resource" выберите строку "New...". На экране появится диалоговая панель "New resource". В списке "Resource type" выберите строку "MENU" и нажмите кнопку "OK". В главном окне приложения Resource Workshop вы увидите несколько окон, предназначенных для проектирования шаблона меню (рис. 1.13).
Рис. 1.13. Проектирование шаблона меню
Окно "TEST MENU" предназначено для визуальной проверки проектируемого меню. С помощью этого окна в любой момент времени вы можете проверить работу созданного вами меню.
Окно MENU_1 содержит текстовое описание создаваемого меню. Первоначально меню состоит из одного временного меню "Pop-up", в котором определена одна строка "Item". Вы можете выбирать мышью строки описания элементов меню, при этом в левой части окна "MENU" вам будет предоставлена возможность изменить название, идентификатор и атрибуты соответствующего элемента.
Для добавления строки во временное меню в окне "MENU_1" выделите строку, после которой надо добавить новую. Затем из меню "Menu" выберите строку "New menu item". Можно добавить горизонтальную разделительную линию. Для этого из меню "Menu" выберите строку "New separator".
Если вам надо добавить новое временное меню, выделите строку "__EndPopup__" и выберите из меню "Menu" строку "New pop-up".
Для удаления строки или временного меню вам достаточно выделить соответствующую строку в окне "MENU_1" и нажать клавишу <Delete>.
Для того чтобы изменить атрибуты строки меню выделите нужную строку в окне "MENU_1" и в левой части окна "MENU:MENU_1" укажите новые атрибуты.
В поле "Item text" вы можете изменить текст, соответствующей строке меню.
В поле "Item id" можно задать идентификатор строки меню. Это может быть любое целое число. Когда вы создаете меню при помощи Resource Workshop, идентификаторы строк меню присваиваются автоматически. Напомним, что идентификатор строки меню передается функции окна приложения в параметре wParam сообщения WM_COMMAND. Пользуясь этим идентификатором, приложение определяет строку, выбранную вами в меню, после чего выполняет необходимые действия в соответствии с логикой работы приложения.
С помощью группы переключателей "Item type" вы можете изменить тип выделенной вами в окне "MENU_1" строки, превратив обычную строку ("Menu item") в разделительную линию ("Separator").
При помощи переключателя "Break before" можно определить для строк принудительный переход на новую строку или в новый столбец.
Если включен переключатель "No break", элементы меню располагаются как обычно, то есть строка нового временного меню добавляется в полосе меню с левой стороны, а новая строка временного меню располагается в нижней части меню.
Если включен переключатель "Menu bar break", строка названия временного меню располагается не слева, а внизу, образуя "второй этаж" полосы меню. Эта возможность используется редко.
Переключатель "Menu break" предназначен для размещения строки временного меню в другом столбце меню.
Переключатель "Help break" задает выравнивание названия временного меню по правой границе полосы меню. В приложениях, созданных для Windows версии 3.0, такое расположение использовалось для меню "Help". Однако в стандартных приложениях Windows версии 3.1 выравнивание меню "Help" по правой границе не выполняется.
С помощью переключателя "Initial state" вы можете задать начальное состояние элемента меню как активное ("Enabled"), неактивное ("Disabled") или неактивное с отображением серым цветом ("Grayed"). Можно также указать, что строка меню должна быть отмечена галочкой. Для этого следует установить переключатель "Checked".
Меню "Menu" приложения Resource Workshop содержит строки "New file pop-up", "New edit pop-up" и "New help pop-up". Эти строки предназначены для автоматического создания стандартных меню "File", "Edit" и "Help", которые должны быть в любом стандартном приложении Windows. Мы воспользовались этими строками для создания меню в нашем приложении Menu Demo, упомянутом выше.
Выбрав из меню "Menu" строку "Check duplicates", вы можете проверить идентификаторы строк созданного вами меню. Если в меню есть строки с одинаковыми идентификаторами, на экране появится диалоговая панель с предупреждающим сообщением.
После завершения процесса формирования меню вы можете изменить имя меню в описании шаблона. Для этого из меню "Resource" выберите строку "Rename". На экране появится диалоговая панель "Rename resource", с помощью которой вы сможете изменить имя меню (рис. 1.14).
Рис. 1.14. Изменение имени меню
После того как вы измените имя меню и нажмете на клавишу "OK", на экране появится диалоговая панель с запросом о необходимости создания символической константы, соответствующей данному имени. В этой панели следует нажать на клавишу "No", так как наше приложение будет ссылаться на имя меню при помощи текстовой строки, а не константы.
Теперь вы знаете, как создать шаблон меню. Следующий этап - подключение меню к окну приложения. Обычно меню определяется для класса окна при регистрации или для отдельного окна при его создании функцией CreateWindow.
Если при регистрации класса окна в поле lpszMenuName структуры типа WNDCLASS указать адрес текстовой строки, содержащей имя шаблона меню в файле ресурсов, все перекрывающиеся и временные окна, создаваемые на базе этого класса, будут иметь меню, определенное данным шаблоном. Дочерние окна (child window) не могут иметь меню.
Например, пусть в файле описания ресурсов шаблон меню определен под именем APP_MENU:
APP_MENU MENU BEGIN .... .... .... END
В этом случае для подключения меню при регистрации класса вы должны записать адрес текстовой строки "APP_MENU" в поле lpszMenuName структуры wc, имеющей тип WNDCLASS:
wc.lpszMenuName = "APP_MENU";
Вы можете использовать для идентификации шаблона меню целые числа (как и для идентификации ресурсов других типов). В этом случае необходимо использовать макрокоманду MAKEINTRESOURCE.
Например, пусть в файле описания ресурсов и в файле исходного текста приложения определена константа:
#define APP_MENU 123
В этом случае ссылка на меню при регистрации класса окна должна выполняться следующим образом:
wc.lpszMenuName = MAKEINTRESOURCE(APP_MENU);
В своих приложениях мы будем пользоваться первым способом, так как он проще в реализации.
Когда для класса окна определено меню, все перекрывающиеся и временные окна, создаваемые на базе этого класса, будут иметь меню, если при создании окна функцией CreateWindow идентификатор меню указан как 0.
Если при регистрации класса окна было определено меню, вы можете создавать окна с этим меню, или можете указать для создаваемого окна другое меню. Для подключения меню, отличного от указанного в классе окна, вам необходимо задать идентификатор нужного меню при создании окна функцией CreateWindow. Короче говоря, окно может иметь меню, определенное в классе, или свое собственное.
Девятый параметр функции CreateWindow используется для подключения меню к создаваемому окну:
hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // размеры и расположение окна CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна hmenu, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры
Значение параметра идентификатора меню может быть получено, например, от функции LoadMenu , определенной в программном интерфейсе Windows:
HMENU WINAPI LoadMenu(HINSTANCE hInstance, LPCSTR lpszMenuName);
Параметр hInstance должен содержать идентификатор текущей копии приложения, полученный через соответствующий параметр функции WinMain.
Параметр lpszMenuName является указателем на строку символов, закрытую двоичным нулем, содержащую имя загружаемого шаблона меню. Если для идентификации шаблона меню используется целое число, необходимо сформировать этот указатель при помощи макрокоманды MAKEINTRESOURCE.
Функция LoadMenu возвращает идентификатор загруженного меню или NULL при ошибке.
Перед завершением своей работы приложение должно уничтожить загруженное меню функцией DestroyMenu :
BOOL WINAPI DestroyMenu(HMENU hmenu);
В качестве единственного параметра функции DestroyMenu необходимо указать идентификатор уничтожаемого меню.
Функция DestroyMenu возвращает в случае успеха значение TRUE, при ошибке - FALSE.
Меню посылает сообщения в функцию создавшего его окна.
Сообщение WM_INITMENU посылается перед отображением меню и может быть использовано для инициализации. Сообщение WM_COMMAND посылается после того, как пользователь выберет одну из активных строк меню. Системное меню посылает в окно приложения сообщение WM_SYSCOMMAND, которое обычно не обрабатывается приложением (передается функции DefWindowProc). В процессе выбора строки из меню, когда курсор перемещается по строкам меню, функция окна, создавшего меню, получает сообщение WM_MENUSELECT. Перед инициализацией временного меню функция окна получает сообщение WM_INITMENUPOPUP.
Из всех этих сообщений наибольший интерес представляют сообщения WM_INITMENU, WM_INITMENUPOPUP, WM_COMMAND, WM_SYSCOMMAND.
Сообщение WM_INITMENU посылается окну, создавшему меню, в момент отображения меню. Это происходит, когда вы нажимаете на строку в полосе меню или активизируете временное меню при помощи клавиатуры.
Вместе с этим сообщением в параметре wParam передается идентификатор активизируемого меню. Параметр lParam не используется.
Если приложение обрабатывает сообщение WM_INITMENU, соответствующий обработчик должен вернуть нулевое значение. Обработка может заключаться в активизации или деактивизации строк меню, изменении состояния строк (отмеченное галочкой или не отмеченное) и т. п. Немного позже мы опишем функции, предназначенные для динамического изменения внешнего вида и состояния меню.
Сообщение WM_INITMENUPOPUP посылается окну, когда операционная система Windows готова отобразить временное меню. Младшее слово параметра lParam содержит порядковый номер временного меню в меню верхнего уровня, старшее слово содержит 1 для системного меню или 0 для обычного меню.
Это сообщение можно использовать для активизации или блокирования отдельных строк временного меню.
Сообщение WM_COMMAND , как мы уже говорили, посылается функции окна приложения, создавшего меню, когда вы выбираете нужную вам строку. Параметр wParam содержит идентификатор строки, определенный в шаблоне меню.
Задача функции окна, обрабатывающей сообщения от меню, заключается в проверке значения параметра wParam и выполнении соответствующей функции.
Сообщение WM_SYSCOMMAND приходит в функцию окна приложения, когда пользователь выбирает строку из системного меню. Параметр wParam, как и для сообщения WM_COMMAND, содержит идентификатор строки меню, в данном случае, идентификатор строки системного меню. Параметр lParam не используется (за исключением идентификатора SC_HOTKEY).
Приведем список идентификаторов с кратким описанием.
Идентификатор |
Описание |
SC_CLOSE |
Удаление окна (строка "Close") |
SC_HOTKEY |
Активизация окна, связанного с
комбинацией клавиш, определенной приложением.
Младшее слово параметра lParam содержит
идентификатор активизируемого окна |
SC_HSCROLL |
Свертка по горизонтали |
SC_KEYMENU |
Выбор из меню при помощи комбинации
клавиш |
SC_MAXIMIZE или SC_ZOOM |
Максимизация окна (строка "Maximize") |
SC_MINIMIZE или SC_ICON |
Минимизация окна (строка "Minimize") |
SC_MOUSEMENU |
Выбор из меню при помощи мыши |
SC_MOVE |
Перемещение окна (строка "Move") |
SC_NEXTWINDOW |
Переключение на следующее окно |
SC_PREVWINDOW |
Переключение на предыдущее окно |
SC_RESTORE |
Восстановление нормального положения и
размера окна |
SC_SCREENSAVE |
Запуск приложения, предназначенного для
предохранения экрана дисплея от
преждевременного выгорания (screen-saver application),
определенного в разделе [boot] файла system.ini |
SC_SIZE |
Изменение размера окна (строка "Size")
|
SC_TASKLIST |
Запуск или активизация приложения Task
Manager |
SC_VSCROLL |
Свертка по вертикали |
При анализе параметра wParam учтите, что младшие четыре бита этого параметра могут принимать любые значения и должны игнорироваться:
if((wParam & 0xfff0) == SC_SIZE) { return 0; }
Скоро мы расскажем вам о том, как можно добавлять строки в системное меню. При добавлении строк в системное меню вы должны указывать идентификатор строки. Этот идентификатор (с учетом сказанного выше относительно младших четырех битов) вы получите в параметре wParam сообщения WM_SYSCOMMAND при выборе добавленной вами строки.
Создав собственный обработчик для сообщений, приходящих от системного меню, вы можете блокировать отдельные или все строки этого меню. Для блокировки какой-либо строки соответствующий обработчик должен вернуть нулевое значение, как в приведенном выше фрагменте кода, блокирующем изменение размера окна.
Теперь, когда мы рассказали вам о том, как создать шаблон меню и само меню, приведем исходные тексты простого приложения, работающего с меню. Оно создает меню, описанное нами ранее в разделе "Создание шаблона меню". Вид главного окна приложения был представлен на рис. 1.9.
В листинге 1.1 приведен исходный текст основного файла приложения.
Листинг 1.1. Файл menu/menu.cpp
// ---------------------------------------- // Работа с меню // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> #include "menu.hpp" // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "MenuClass"; // Заголовок окна char const szWindowTitle[] = "Menu Demo"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc)); // Подключаем меню wc.lpszMenuName = "APP_MENU"; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPABOUT: case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_EDITPASTE: case CM_EDITCOPY: case CM_EDITCUT: case CM_EDITUNDO: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: case CM_FILESAVEAS: case CM_FILESAVE: case CM_FILEOPEN: case CM_FILENEW: { // На сообщение от любой строки меню кроме // завершающей работу программы реагируем // выводом сообщения об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK); return 0; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } case WM_DESTROY: { PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Функция WinMain регистрирует класс окна и создает на его базе главное окно приложения.
При регистрации класса окна указывается имя шаблона меню, определенного в файле описания ресурсов:
wc.lpszMenuName = "APP_MENU";
В остальном функция WinMain не имеет никаких особенностей.
Функция главного окна WndProc обрабатывает сообщение WM_COMMAND, поступающее от меню. Для всех идентификаторов, кроме CM_FILEEXIT, обработка сводится к выводу сообщения о том, что данная функция не реализована. Если вы из меню 'File" нашего приложения выбираете строку "Exit", обработчик сообщения WM_COMMAND уничтожает главное окно приложения, вызывая функцию DestroyWindow. Это приводит к завершению работы приложения.
Идентификаторы строк меню описаны в файле menu.hpp (листинг 1.2), включаемом при помощи оператора #include в главный файл приложения и файл описания ресурсов.
Листинг 1.2. Файл menu/menu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341 #define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321 #define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331
Файл описания ресурсов (листинг 1.3) содержит определение шаблона меню с именем APP_MENU, описанный нами ранее.
Листинг 1.3. Файл menu/menu.rc
#include "menu.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM "&Open...", CM_FILEOPEN MENUITEM "&Save", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE END POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, INACTIVE MENUITEM "&Keyboard", CM_HELPKEYBOARD, INACTIVE MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
Проверяя работу приложения, обратите внимание на то, что хотя в строках временного меню "Edit" были указаны комбинации клавиш ускоренного выбора (например, для функции "Undo" указана комбинация клавиш "Ctrl+Z"), вы пока не можете их использовать. Это связано с тем, что мы пока не определили комбинации клавиш ускоренного выбора, а всего лишь записали их обозначение в строках меню. Мы еще вернемся к этому вопросу.
В листинге 1.4 приведен файл определения модуля, использованный при сборке приложения MENU.
Листинг 1.4. Файл menu/menu.def
; ============================= ; Файл определения модуля ; ============================= NAME MENU DESCRIPTION 'Приложение MENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
В программном интерфейсе операционной системы Windows есть функции, специально предназначенные для работы с меню. С помощью этих функций приложение может создавать меню (даже не имея его шаблона), добавлять или удалять строки или временные меню, активизировать или блокировать отдельные строки меню, изменять состояние строк (отмеченное или не отмеченное) и т. д.
В этом разделе мы расскажем вам о том, как пользоваться такими функциями.
Даже если в файле описания ресурсов нет определения шаблона меню, приложение может создать меню "с нуля" для любого своего перекрывающегося или временного окна (но не для дочернего). Для создания пустого меню (то есть меню, не содержащего ни одной строки и ни одного временного меню) можно воспользоваться функцией CreateMenu :
HMENU WINAPI CreateMenu(void);
Функция возвращает идентификатор созданного меню или NULL при ошибке.
Как правило, в меню верхнего уровня (в меню приложения) создаются временные меню. Для создания временного меню воспользуйтесь функцией CreatePopupMenu:
HMENU WINAPI CreatePopupMenu (void);
В дальнейшем вы можете добавить в меню верхнего уровня созданные функцией CreatePopupMenu временные меню или отдельные строки, вызвав функцию AppendMenu.
Перед завершением работы приложение должно удалить созданные описанным выше способом меню, для чего следует воспользоваться функцией DestroyMenu.
Для подключения к окну с идентификатором hwnd меню с идентификатором hmenu вы можете воспользоваться функцией SetMenu:
BOOL WINAPI SetMenu (HWND hwnd, HMENU hmenu);
Перед вызовом этой функции вы должны загрузить меню и получить его идентификатор, например, при помощи функции LoadMenu.
Функция SetMenu возвращает TRUE при успешном завершении и FALSE при ошибке.
Для добавления строк в созданные функциями CreateMenu и CreatePopupMenu пустые меню можно воспользоваться функцией AppendMenu :
BOOL WINAPI AppendMenu(HMENU hmenu, UINT fuFlags, UINT idNewItem, LPCSTR lpszNewItem);
Параметр hmenu указывает идентификатор меню, к которому добавляется строка или временное меню. Вы должны использовать значение, полученное от функций CreateMenu и CreatePopupMenu.
Параметр fuFlags определяет атрибуты создаваемого элемента меню. Можно указывать следующие значения (соответствующие символические константы описаны в файле windows.h):
Константа |
Описание |
MF_BITMAP |
Для изображения строки меню
используется графическое изображение bitmap. Если
указан этот параметр, младшее слово параметра
lpszNewItem содержит идентификатор изображения |
MF_CHECKED |
При выводе меню на экран строка меню
отмечается галочкой "" |
MF_DISABLED |
Строка меню отображается в нормальном
виде (не серым цветом), но находится в неактивном
состоянии |
MF_ENABLED |
Строка меню разблокирована и
отображается в нормальном виде |
MF_GRAYED |
Строка меню отображается серым цветом и
находится в неактивном состоянии. Такую строку
нельзя выбрать |
MF_MENUBREAK |
Если описывается меню верхнего уровня,
элемент меню выводится с новой строки. Если
описывается временное меню, элемент меню
выводится в новом столбце |
MF_MENUBARBREAK |
Аналогично MF_MENUBREAK, но дополнительно
новый столбец отделяется вертикальной линией
(используется при создании временных меню) |
MF_OWNERDRAW |
Строка меню рисуется окном, создавшем
меню. Когда меню отображается в первый раз,
функция этого окна получает сообщение WM_MEASUREITEM, в
ответ на которое функция окна должна сообщить
размеры области, занимаемой изображением строки
меню. Рисовать изображение строки надо тогда,
когда в функцию окна придет сообщение WM_DRAWITEM.
Флаг MF_OWNERDRAW можно указывать только для временных
меню |
MF_POPUP |
С данным элементом меню связывается
временное меню. Если используется этот флаг,
параметр idNewItem должен содержать идентификатор
временного меню, связанного с данным элементом |
MF_SEPARATOR |
Используется для создания
горизонтальной разделительной линии во
временных меню. Если указан этот флаг, параметры
lpszNewItem и idNewItem не используются |
MF_STRING |
Элемент меню является строкой символов.
Параметр lpszNewItem должен указывать на строку
символов, закрытую двоичным нулем |
MF_UNCHECKED |
При выводе меню на экран строка не
отмечается галочкой "" |
Вы можете указывать сразу несколько флагов, объединив их операцией логического ИЛИ, однако следует иметь в виду, что существует четыре группы взаимно несовместимых флагов:
MF_DISABLED, MF_ENABLED, MF_GRAYED |
MF_BITMAP, MF_OWNERDRAW, MF_STRING |
MF_MENUBREAK, MF_MENUBARBREAK |
MF_CHECKED, MF_UNCHECKED |
Назначение параметра idNewItem зависит от параметра fuFlags. Если значение параметра fuFlags не равно MF_POPUP, через idNewItem вы должны передать идентификатор создаваемой строки меню. Этот идентификатор будет записан в параметр wParam сообщения WM_COMMAND при выборе данной строки. Если же значение параметра fuFlags равно MF_POPUP, через параметр idNewItem вы должны передать функции AppendMenu идентификатор временного меню.
Назначение параметра lpszNewItem также зависит от параметра fuFlags. Если этот параметр равен MF_STRING, параметр lpszNewItem должен указывать на строку символов, закрытую двоичным нулем, если MF_BITMAP - младшее слово параметра lpszNewItem содержит идентификатор изображения, а если параметр fuFlags равен MF_OWNERDRAW, приложение должно передать через параметр lpszNewItem 32-битовое значение, идентифицирующее строку меню.
Еще одна функция, предназначенная для добавления элементов в меню, называется InsertMenu . Эта функция может добавить элемент в середину меню, сдвинув вниз уже существующие элементы.
Приведем прототип функции InsertMenu:
BOOL WINAPI InsertMenu(HMENU hmenu, UINT idItem, UINT fuFlags, UINT idNewItem, LPCSTR lpszNewItem);
Параметры этой функции аналогичны параметрам функции AppendMenu, за исключением параметров idItem и fuFlags.
Параметр idItem определяет элемент меню, перед которым должен быть вставлен новый элемент. Интерпретация этого параметра зависит от значения параметра fuFlags.
В дополнение к возможным значениям параметра fuFlags, описанным нами для функции AppendMenu, вместе с функцией InsertMenu вы можете использовать еще два - MF_BYCOMMAND и MF_BYPOSITION.
Если указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор элемента меню, перед которым будет вставлен новый элемент.
Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер элемента меню, перед которым будет вставлен новый элемент. Для того чтобы добавить элемент в конец меню, для параметра idItem можно указать значение -1.
После внесения всех изменений в меню приложение должно вызвать функцию DrawMenuBar :
void WINAPI DrawMenuBar(HWND hwnd);
Эта функция перерисовывает полосу меню для указанного параметром hwnd окна. В качестве параметра функции следует передать идентификатор окна, создавшего меню.
Для изменения строк (элементов) существующего меню вы можете воспользоваться функцией ModifyMenu :
BOOL WINAPI ModifyMenu(HMENU hmenu, UINT idItem, UINT fuFlags, UINT idNewItem, LPCSTR lpszNewItem);
Параметры этой функции идентичны параметрам функции InsertMenu.
Функция ModifyMenu заменяет указанный элемент меню на новый. При замещении временного меню оно уничтожается и все связанные с ним ресурсы освобождаются.
После того как вы изменили меню, не забудьте вызывать функцию DrawMenuBar, описанную выше.
В программном интерфейсе Windows версии 3.0 была определена функция ChangeMenu , предназначенная для изменения существующего меню. В версии 3.1 эта функция была заменена на следующие пять функций:
Функция |
Описание |
AppendMenu |
Добавление элемента в меню |
DeleteMenu |
Удаление элемента из меню |
InsertMenu |
Вставка элемента в меню |
ModifyMenu |
Изменение элемента меню |
RemoveMenu |
Удаление элемента меню без освобождения
ресурсов, занимаемых этим элементом |
Новые приложения не должны пользоваться функцией ChangeMenu.
Для удаления элементов меню, таких, как строки и временные меню, предназначена функция DeleteMenu :
BOOL WINAPI DeleteMenu(HMENU hmenu, UINT idItem, UINT fuFlags);
Параметр hmenu определяет меню, из которого будет удален элемент.
Параметр idItem определяет удаляемый элемент, причем его интерпретация зависит от значения параметра fuFlags.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор удаляемого элемента меню. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер удаляемого элемента меню.
При удалении временного меню все связанные с ним ресурсы освобождаются.
Для отображения результата удаления меню следует вызвать функцию DrawMenuBar.
В программном интерфейсе Windows определена функция RemoveMenu , имеющая параметры, аналогичные параметрам функции DeleteMenu:
BOOL WINAPI RemoveMenu(HMENU hmenu, UINT idItem, UINT fuFlags);
Эта функция удаляет указанный ей элемент из меню, но не уничтожает связанные с ним ресурсы, поэтому вы можете вновь воспользоваться удаленным элементом меню (если знаете его идентификатор, о том как получить идентификатор временного меню мы расскажем немного позже).
Напомним, что для уничтожения меню используется функция DestroyMenu :
BOOL WINAPI DestroyMenu(HMENU hmenu);
В качестве параметра функции передается идентификатор уничтожаемого меню. Функция освобождает все ресурсы, связанные с уничтоженным меню.
Для изменения состояния элемента меню удобно использовать функцию EnableMenuItem :
BOOL WINAPI EnableMenuItem(HMENU hmenu, UINT idItem, UINT uEnable);
Параметр hmenu указывает идентификатор меню, над элементом которого будет выполняться операция активизации или блокирования.
Параметр idItem определяет элемент меню, над которым выполняется операция. Интерпретация этого параметра зависит от значения параметра uEnable.
Параметр uEnable может принимать значения MF_DISABLED, MF_ENABLED или MF_GRAYED в комбинации с одним из значений: MF_BYCOMMAND или MF_BYPOSITION.
Для блокирования элемента меню необходимо использовать значение MF_DISABLED. Если заблокированный элемент меню нужно изобразить серым цветом, вместо MF_DISABLED используйте значение MF_GRAYED.
Для активизации заблокированного ранее элемента меню укажите значение MF_ENABLED.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор элемента меню, состояние которого будет изменено. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер элемента меню, состояние которого будет изменено.
Как и после выполнения других операций по изменению меню, после изменения состояния элемента меню необходимо вызвать функцию DrawMenuBar, которая отобразит внесенные изменения на экране.
Вы знаете, что элементы временного меню могут быть отмечены галочкой. Для включения и выключения такой отметки можно использовать функцию CheckMenuItem :
BOOL WINAPI CheckMenuItem(HMENU hmenu, UINT idItem, UINT fuCheck);
Параметр hmenu указывает идентификатор меню, над элементом которого будет выполняться операция включения или выключения отметки.
Параметр idItem определяет элемент меню, над которым выполняется операция. Интерпретация этого параметра зависит от значения параметра fuCheck.
Параметр fuCheck может принимать значения MF_CHECKED или MF_UNCHECKED в комбинации с одним из значений: MF_BYCOMMAND или MF_BYPOSITION.
Для включения отметки элемента меню необходимо использовать значение MF_CHECKED. Для выключения отметки элемента меню укажите значение MF_UNCHECKED.
Если в параметре fuCheck указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор элемента меню, отметка которого будет изменена. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер элемента меню, отметка которого будет изменена.
Для выделения строк меню верхнего уровня, расположенных в полосе меню ниже заголовка окна, можно использовать функцию HiliteMenuItem :
BOOL WINAPI HiliteMenuItem(HWND hwnd, HMENU hmenu, UINT idItem, UINT fuHilite);
Параметр hwnd должен содержать идентификатор окна, для которого выполняется операция выделения.
Через параметр hMenu необходимо передать идентификатор соответствующего меню верхнего уровня.
Параметр idItem определяет элемент меню, над которым выполняется операция выделения. Интерпретация этого параметра зависит от значения параметра fuHilite.
Параметр fuHilite может принимать значения MF_HILITE или MF_UNHILITE в комбинации с одним из значений: MF_BYCOMMAND или MF_BYPOSITION.
Для выделения строки меню необходимо использовать значение MF_HILITE. Для отмены выделения строки меню укажите значение MF_UNHILITE.
Если в параметре fuHilite указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор строки меню, для которого выполняется операция выделения или отмены выделения. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер этой строки.
В программном интерфейсе операционной системы Windows существует несколько функций для получения различной информации о меню и о состоянии строк меню.
С помощью функции GetMenu вы можете определить идентификатор меню, связанного с окном:
HMENU WINAPI GetMenu(HWND hwnd);
Идентификатор окна задается при помощи параметра hwnd.
Функция возвращает идентификатор меню или NULL, если окно не имеет меню. Дочернее окно не может иметь меню, однако в документации к SDK говорится, что если вы вызовете данную функцию для дочернего окна, возвращенное значение будет неопределено.
Для определения идентификатора временного меню следует вызвать функцию GetSubMenu :
HMENU WINAPI GetSubMenu(HMENU hmenu, int nPos);
Эта функция для меню верхнего уровня с идентификатором hmenu возвращает идентификатор временного меню, порядковый номер которого задается параметром nPos. Первому временному меню соответствует нулевое значение параметра nPos.
Если функция GetSubMenu вернула значение NULL, то меню верхнего уровня не содержит в указанной позиции временное меню.
Для того чтобы убедиться, что идентификатор не является идентификатором меню, вы можете использовать функцию IsMenu :
BOOL WINAPI IsMenu(HMENU hmenu);
Эта функция появилась в программном интерфейсе Windows версии 3.1.
Функция возвращает значение FALSE, если переданный ей через параметр hmenu идентификатор не является идентификатором меню. Можно было бы ожидать, что если функция IsMenu вернула значение TRUE, то проверяемый идентификатор является идентификатором меню, однако в описании функции сказано, что это не гарантируется.
Функция GetMenuItemCount возвращает количество элементов в меню верхнего уровня или во временном меню, заданном параметром hmenu:
int WINAPI GetMenuItemCount(HMENU hmenu);
Для получения идентификатора элемента меню, расположенного в указанной позиции, вы можете воспользоваться функцией GetMenuItemID :
UINT WINAPI GetMenuItemID(HMENU hmenu, int nPos);
Параметр hmenu задает меню, идентификатор элемента которого требуется определить. Порядковый номер элемента определяется параметром nPos, причем первому элементу соответствует нулевое значение. В случае ошибки (если параметр hmenu указан как NULL или указанный элемент является временным меню) функция GetMenuItemID возвращает значение -1. Если вы попытаетесь определить идентификатор горизонтальной разделительной линии (сепаратора), функция вернет нулевое значение.
С помощью функции GetMenuString вы можете переписать в буфер текстовую строку, соответствующую элементу меню.
int WINAPI GetMenuString(HMENU hmenu, UINT idItem, LPSTR lpsz, int cbMax, UINT fuFlags);
Параметр hmenu определяет меню, для которого будет выполняться операция.
Параметр idItem определяет элемент меню, над которым выполняется операция. Интерпретация этого параметра зависит от значения параметра fuFlags.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор строки меню, для которого выполняется операция. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер этой строки.
Адрес буфера, в который будет выполняться копирование, задается параметром lpsz, размер буфера без учета двоичного нуля, закрывающего строку, - оператором cbMax. Символы, не поместившиеся в буфер, будут обрезаны.
Функция GetMenuString возвращает количество символов, скопированных в буфер, без учета двоичного нуля, закрывающего строку.
Функция GetMenuState возвращает флаги состояния для заданного элемента меню:
UINT WINAPI GetMenuState(HMENU hmenu, UINT idItem, UINT fuFlags);
Параметр hmenu определяет меню, для которого будет выполняться операция.
Параметр idItem определяет элемент меню, для которого будут получены флаги состояния. Интерпретация этого параметра зависит от значения параметра fuFlags.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор строки меню, для которого выполняется операция. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер этой строки.
Для временного меню старший байт возвращаемого функцией значения содержит количество элементов во временном меню, а младший - набор флагов, описывающих временное меню. Для меню верхнего уровня возвращаемое значение является набором флагов, описывающих указанный элемент меню:
Флаг |
Описание |
MF_BITMAP |
Для изображения строки меню
используется графическое изображение bitmap |
MF_CHECKED |
Строка меню отмечена галочкой "" |
MF_DISABLED |
Строка меню находится в неактивном
состоянии |
MF_ENABLED |
Строка меню разблокирована и
отображается в нормальном виде. Этому состоянию
соответствует возвращаемое функцией GetMenuState
значение, равное нулю |
MF_GRAYED |
Строка меню отображается серым цветом и
находится в неактивном состоянии. Такую строку
нельзя выбрать |
MF_MENUBREAK |
Для меню верхнего уровня элемент меню
выводится с новой строки. Для временного меню
элемент выводится в новом столбце |
MF_MENUBARBREAK |
Аналогично MF_MENUBREAK, но дополнительно
столбец отделен вертикальной линией |
MF_SEPARATOR |
Строка является горизонтальной
разделительной линией во временных меню |
MF_UNCHECKED |
Строка не отмечена галочкой "" |
Если указанный элемент меню не существует, функция GetMenuState возвращает значение -1.
В приложении DMENU, имитирующем работу с документами (например, с текстами), мы использовали большинство описанных выше функций, предназначенных для динамического создания и изменения меню. Проект этого приложения не включает файл описания ресурсов и, соответственно, не использует шаблон меню.
Сразу после запуска приложения в полосе меню определены два временных меню - "File" и "Help" (рис. 1.15). В меню "File" вы можете использовать строки "New" и "Open", предназначенные, соответственно, для создания нового документа или загрузки документа из файла. Кроме этих двух строк вы можете выбрать строку "Exit", завершающую работу приложения. Строка "Demo Version" заблокирована и отмечена галочкой. Так как мы еще не научились работать с принтером, строки "Print", "Page Setup" и "Printer Setup" заблокированы и отображаются серым цветом.
Рис. 1.15. Исходный вид меню приложения DMENU
Пока вы не создали новый документ или не загрузили документ, созданный ранее, строки "Close", "Save", "Save as..." заблокированы. Так как документ не загружен, его нельзя закрыть или сохранить, поэтому соответствующие строки в меню отображаются серым цветом.
После того, как вы выберете строку "New" или "Open", внешний вид меню приложения изменится (рис. 1.16).
Рис. 1.16. Изменения в меню приложения DMENU
Так как приложение DMENU рассчитано на "работу" с одним документом, после загрузки документа строки "New" и "Open" блокируются. Для их разблокирования вы должны закрыть документ, выбрав строку "Close". В этом случае меню приложения примет исходный вид, представленный на рис. 1.15.
После загрузки документа в меню "File" разблокируются строки "Close", "Save" и "Save as...". Кроме этого, появляется новое временное меню "Edit", аналогичное меню "Edit" предыдущего приложения. Меню "Edit" присутствует в окне только тогда, когда загружен документ. Если документ не загружен, то редактировать нечего. В этом случае меню "Edit" не нужно.
Многие приложения Windows изменяют меню похожим образом. Внешний вид меню может зависеть от типа обрабатываемого документа, от используемых параметров и режимов (краткое меню, полное меню, расширенное меню и т. д.) или от текущего состояния документа.
Исходный текст главного файла приложения DMENU, реализующего описанный выше алгоритм изменения меню, представлен в листинге 1.5.
Листинг 1.5. Файл dmenu/dmenu.cpp
// ---------------------------------------- // Создание меню без использования шаблона // Динамическое изменение меню // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> #include "dmenu.hpp" // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "DMenuClass"; // Заголовок окна char const szWindowTitle[] = "Menu Demo"; // Идентификатор меню верхнего уровня HMENU hmenu; // Идентификаторы временных меню HMENU hmenuFile; // "File" HMENU hmenuEdit; // "Edit" HMENU hmenuHelp; // "Help" // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // Создаем пустое меню верхнего уровня hmenu = CreateMenu(); // Подключаем меню к главному окну приложения SetMenu(hwnd, hmenu); // Создаем два временных меню - "File" и "Help" hmenuFile = CreatePopupMenu(); hmenuHelp = CreatePopupMenu(); // Добавляем строки к меню "File" AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open"); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILECLOSE, "&Close"); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILESAVE, "&Save"); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILESAVEAS, "Save &as..."); // Добавляем разделительную линию AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPRINT, "&Print"); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPAGE_SETUP, "Page Se&tup"); AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPRINTER_SETUP, "P&rinter Setup"); AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuFile, MF_DISABLED | MF_STRING, CM_FILEDEMO, "&Demo Version"); AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEEXIT, "E&xit"); // Отмечаем галочкой строку "Demo Version" CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED); // Добавляем строки к меню "Help" AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPINDEX, "&Index\tF1"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPKEYBOARD, "&Keyboard"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPCOMMANDS, "&Commands"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPPROCEDURES, "&Procedures"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPUSING_HELP, "&Using help"); AppendMenu(hmenuHelp, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuHelp, MF_ENABLED | MF_STRING, CM_HELPABOUT, "&About..."); // Добавляем временные меню к меню верхнего уровня AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File"); AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help"); // Записываем в идентификатор меню "Edit" значение // NULL. Если это меню не будет создано, мы не будем // вызывать функцию DestroyMenu для его уничтожения hmenuEdit = NULL; // Перерисовываем меню DrawMenuBar(hwnd); return 0; } case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_EDITPASTE: case CM_EDITCOPY: case CM_EDITCUT: case CM_EDITUNDO: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: case CM_FILESAVEAS: case CM_FILESAVE: { // Выводим сообщение об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK); return 0; } // Выбрали строку "About..." в меню "Help" case CM_HELPABOUT: { MessageBox(hwnd, "Приложение DMENU\n(C) Фролов А.В., 1994", szWindowTitle, MB_OK | MB_ICONINFORMATION); return 0; } // Выбрали строки "Open" или "New" в меню "File" case CM_FILEOPEN: case CM_FILENEW: { // Создаем временное меню "Edit" hmenuEdit = CreatePopupMenu(); // Добавляем строки в меню "Edit" AppendMenu(hmenuEdit, MF_GRAYED | MF_STRING, CM_EDITUNDO, "&Undo\tCtrl+Z"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCUT, "&Cut\tCtrl+X"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCOPY, "&Copy\tCtrl+C"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITPASTE, "&Paste\tCtrl+V"); // Вставляем меню "Edit" между меню "File" // и в меню "Help" InsertMenu(hmenu, 1, MF_BYPOSITION | MF_ENABLED | MF_POPUP, (UINT)hmenuEdit, "&Edit"); // Разблокируем строки "Save", "Save as..." // и "Close" в меню "File" EnableMenuItem(hmenuFile, CM_FILESAVE, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_ENABLED | MF_BYCOMMAND); // Блокируем строки "New" и "Open" в меню "File" EnableMenuItem(hmenuFile, CM_FILENEW, MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_GRAYED | MF_BYCOMMAND); // Перерисовываем меню DrawMenuBar(hwnd); return 0; } // Выбрали строку "Close" из меню "File" case CM_FILECLOSE: { // Уничтожаем временное меню "Edit" DestroyMenu(hmenuEdit); // Удаляем соответствующую строку из меню // верхнего уровня RemoveMenu(hmenu, 1, MF_BYPOSITION); // Блокируем строки "Save", "Save as..." // и "Close" в меню "File" EnableMenuItem(hmenuFile, CM_FILESAVE, MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_GRAYED | MF_BYCOMMAND); // Разблокируем строки "New" и "Open" в меню "File" EnableMenuItem(hmenuFile, CM_FILENEW, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_ENABLED | MF_BYCOMMAND); // Перерисовываем меню DrawMenuBar(hwnd); return 0; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } case WM_DESTROY: { // Если было создано меню "Edit", // уничтожаем его if(hmenuEdit != NULL) { DestroyMenu(hmenuEdit); } // Уничтожаем созданные ранее меню DestroyMenu(hmenuFile); DestroyMenu(hmenuHelp); DestroyMenu(hmenu); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
В приложении определены четыре глобальные переменные типа HMENU, предназначенные для хранения идентификаторов одного меню верхнего уровня (переменная hmenu) и трех временных меню (переменные hmenuFile, hmenuEdit, hmenuHelp).
Меню верхнего уровня создается в функции главного окна приложения во время обработки сообщения WM_CREATE. Созданное пустое меню подключается к главному окну приложения при помощи функции SetMenu:
hmenu = CreateMenu(); SetMenu(hwnd, hmenu);
Далее создаются два временных меню - "File" и "Help", для чего два раза вызывается функция CreatePopupMenu:
hmenuFile = CreatePopupMenu(); hmenuHelp = CreatePopupMenu();
На данный момент все меню пустые. Прежде всего обработчик сообщения WM_CREATE добавляет к меню "File" несколько строк, вызывая соответствующее число раз функцию AppendMenu:
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open"); ....... и т. д. .......
Обратите внимание на способ, которым в меню добавляется разделительная линия:
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
Если в качестве второго параметра функции AppendMenu указано значение MF_SEPARATOR, третий и четвертый параметр этой функции игнорируются.
Для того чтобы отметить галочкой строку "Demo Version", вызывается функция CheckMenuItem:
CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
Аналогичным образом формируется меню "Help".
Далее сформированные временные меню "File" и "Help" добавляются к меню верхнего уровня при помощи функции AppendMenu:
AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File"); AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help");
В заключение вызывается функция DrawMenuBar, которая отображает внесенные изменения на экране:
DrawMenuBar(hwnd);
После формирования меню от него в функцию окна начинают поступать сообщения WM_COMMAND.
Так как меню, которые вы создаете, занимают системные ресурсы, их необходимо уничтожать, если они больше не нужны. При завершении работы приложения мы удалим все созданные меню. Однако меню "Edit" может так и не быть создано, так как вы можете сразу после запуска завершить работу приложения. Для того чтобы определить, нужно ли удалять меню "Edit", мы при создании главного окна приложения записываем в переменную hmenuEdit, предназначенную для хранения идентификатора меню, значение NULL:
hmenuEdit = NULL;
Если меню "Edit" будет создано, в переменную hmenuEdit будет записано значение соответствующего идентификатора. При завершении работы приложения мы проверим состояние этой переменной и, если ее содержимое отлично от значения NULL, уничтожим меню.
На многие из этих сообщений функция окна реагирует выводом сообщения о том, что данная функция не реализована. При выборе строки "About..." в меню "Help" на экран выводится диалоговая панель с сообщением о названии приложения и сведения о разработчике. Это стандартная реакция на выбор строки "About..." в меню "Help" любого приложения Windows.
Когда вы выбираете из меню "File" строки "New" или "Open", в функцию окна приложения приходит сообщение WM_COMMAND со значением парамера wParam, равным, соответственно, CM_FILENEW и CM_FILEOPEN. В ответ на эти сообщения создается новое временное меню "Edit", которое вставляется между временными меню "File" и временным меню "Help":
hmenuEdit = CreatePopupMenu(); AppendMenu(hmenuEdit, MF_GRAYED | MF_STRING, CM_EDITUNDO, "&Undo\tCtrl+Z"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCUT, "&Cut\tCtrl+X"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCOPY, "&Copy\tCtrl+C"); AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITPASTE, "&Paste\tCtrl+V");
Для вставки меню вызывается функция InsertMenu:
InsertMenu(hmenu, 1, MF_BYPOSITION | MF_ENABLED | MF_POPUP, (UINT)hmenuEdit, "&Edit");
В качестве второго параметра этой функции передается значение 1. Так как в третьем параметре указан флаг MF_BYPOSITION, функция вставит меню перед временным меню с номером 1, т. е. перед меню "Help".
Затем в меню "File" разблокируются строки "Save", "Save as...", "Close" и блокируются строки "New" и "Open":
EnableMenuItem(hmenuFile, CM_FILESAVE, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_ENABLED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILENEW, MF_GRAYED | MF_BYCOMMAND); EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_GRAYED | MF_BYCOMMAND);
В заключение вызывается функция DrawMenuBar, отображающая внесенные в меню изменения.
Если вы выберите из меню "File" строку "Close", функция окна получит сообщение WM_COMMAND со значением параметра wParam, равным CM_FILECLOSE. Соответствующий обработчик уничтожает временное меню "Edit" (документ закрыт, редактировать нечего), и удаляет соответствующую строку из меню верхнего уровня:
DestroyMenu(hmenuEdit); RemoveMenu(hmenu, 1, MF_BYPOSITION);
После этого в меню "File" блокируются строки "Save", "Save as...", "Close" и разблокируются строки "New" и "Open". Для этой цели вызывается функция EnableMenuItem. Для отображения внесенных изменений вызывается функция DrawMenuBar.
При завершении работы приложения мы проверяем содержимое переменной hmenuEdit. Если в момент завершения работы приложения меню "Edit" не существует, в этой переменной находится значение NULL. В этом случае мы не вызываем функцию DestroyWindow. Остальные меню уничтожаются всегда:
case WM_DESTROY: { if(hmenuEdit != NULL) { DestroyMenu(hmenuEdit); } DestroyMenu(hmenuFile); DestroyMenu(hmenuHelp); DestroyMenu(hmenu); PostQuitMessage(0); return 0; }
Несмотря на то, что при уничтожении окна все связанные с ним меню также уничтожаются, будет лучше, если ваше приложение удалит все созданные им объекты самостоятельно. Такое поведение отвечает правилам "хорошего тона" для приложений Windows, которые совместно используют многие системные ресурсы.
Идентификаторы строк меню описаны во включаемом файле dmenu.hpp (листинг 1.6).
Листинг 1.6. Файл dmenu/dmenu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341 #define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321 #define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331 #define CM_FILECLOSE 24330 #define CM_FILEDEMO 24329
Файл определения модуля приложения приведен в листинге 1.7.
Листинг 1.7. Файл dmenu/dmenu.rc
; ============================= ; Файл определения модуля ; ============================= NAME DMENU DESCRIPTION 'Приложение DMENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
При необходимости вы можете изменить системное меню (рис. 1.6), добавив в него новые строки или горизонтальные разделительные линии.
Прежде всего вам надо получить идентификатор системного меню. Это можно сделать при помощи функции GetSystemMenu :
HMENU WINAPI GetSystemMenu(HWND hwnd, BOOL fRevert);
Параметр hwnd является идентификатором окна, к системному меню которого требуется получить доступ.
Параметр fRevert определяет действия, выполняемые функцией GetSystemMenu. Если этот параметр указан как FALSE, функция GetSystemMenu возвращает идентификатор используемой на момент вызова копии системного меню. Если же значение этого параметра равно TRUE, функция восстанавливает исходный вид системного меню, используемый в Windows по умолчанию и уничтожает все созданные ранее копии системного меню. В последнем случае возвращаемое значение не определено.
После того как вы получили идентификатор системного меню, вы можете использовать функции AppendMenu, InsertMenu или ModifyMenu для изменения внешнего вида системного меню.
Есть одна особенность, которую нужно учитывать при добавлении собственной строки в системное меню. Как мы уже говорили, младшие четыре бита в сообщении WM_SYSCOMMAND могут иметь любые значения. С учетом этого обстоятельства следует выбирать идентификатор для добавляемой в системное меню строки. Очевидно, что значение этого идентификатора должно быть больше 15 и не должно конфликтовать с идентификаторами других строк меню приложения.
Приложение SMARTPAD, которое мы рассмотрим немного позже, добавляет в системное меню разделительную линию и новую строку, а также блокирует строку "Close", предназначенную для удаления окна.
Вначале в этом приложении мы определяем идентификатор системного меню, вызывая функцию GetSystemMenu:
hmenuSystemMenu = GetSystemMenu(hwnd, FALSE);
Далее к системному меню добавляется разделительная линия и строка "About...", для чего используется уже знакомая вам функция AppendMenu:
AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0); AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED, CM_SYSABOUT, "&About...");
В качестве идентификатора мы использовали значение 0x8880 (младшие четыре бита равны нулю):
#define CM_SYSABOUT 0x8880
Для блокирования строки "Close" мы вызываем функцию EnableMenuItem, указывая ей в качестве первого параметра идентификатор системного меню:
EnableMenuItem(hmenuSystemMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
Обработчик сообщения WM_SYSCOMMAND, определенный в функции главного окна приложения SMARTPAD, проверяет значение параметра wParam на совпадение с идентификатором добавленной нами строки без учета младших четырех бит:
case WM_SYSCOMMAND: { if((wParam & 0xfff0) == CM_SYSABOUT) { lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst); DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc); return 0; } else if((wParam & 0xfff0) == SC_CLOSE) return 0; break; }
Для того чтобы заблокировать строку "Close", мы выполняем обработку сообщения WM_SYSCOMMAND с параметром wParam, равным SC_CLOSE (идентификатор стандартной строки "Close" в системном меню). Обработка заключается в возврате нулевого значения.
Так как ранее мы уже заблокировали эту строку при помощи функции EnableMenuItem, нет необходимости выполнять еще одну блокировку в обработчике сообщения WM_SYSCOMMAND. Мы сделали это исключительно для иллюстрации возможности блокировки строки системного меню при обработке сообщения WM_SYSCOMMAND.
При необходимости ваше приложение может создать временное плавающее меню , расположенное в любом месте экрана (рис. 1.7).
В приложении SMARTPAD мы создаем плавающее меню, когда пользователь нажимает в окне редактирования текста правую клавишу мыши. Процедура создания меню выглядит следующим образом:
if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt; pt = MAKEPOINT(lParam); ClientToScreen(hwnd, &pt); hmenuPopup = CreatePopupMenu(); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save"); AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit"); TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL); DestroyMenu(hmenuPopup); }
Обработчик сообщения WM_RBUTTONDOWN, которое приходит, если вы нажимаете правую клавишу мыши, прежде всего преобразует координаты курсора мыши в экранные. Для этого он вызывает функцию ClientToScreen .
Далее при помощи функции CreatePopupMenu создается пустое временное меню. Это меню наполняется обычным образом с помощью функции AppendMenu, но оно не привязывается к главному меню приложения или какому-либо другому меню. Вместо этого создается плавающее меню. Для этого идентификатор созданного и наполненного временного меню передается функции TrackPopupMenu :
TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL);
Эта функция выводит на экран плавающее меню и создает свой собственный цикл обработки сообщений, завершающий работу после выбора строки. Поэтому функция TrackPopupMenu не возвращает управление до тех пор, пока работа с меню не будет завершена либо выбором строки, либо отказом от выбора.
После этого созданное временное меню уничтожается:
DestroyMenu(hmenuPopup);
Приведем прототип функции TrackPopupMenu :
BOOL WINAPI TrackPopupMenu(HMENU hmenu, UINT fuFlags, int x, int y, int nReserved, HWND hwnd, const RECT FAR* lprc);
Параметр hmenu должен содержать идентификатор отображаемого временного меню. Вы можете создать новое меню при помощи функции CreatePopupMenu или получить идентификатор существующего временного меню, вызвав функцию GetSubMenu.
Параметр fuFlags определяет расположение плавающего меню и клавиши мыши, с помощью которых должен выполняться выбор.
Для определения расположения меню вы можете указать один из трех флагов:
Флаг |
Описание |
TPM_CENTERALIGN |
Центровка относительно координаты,
заданной параметром x |
TPM_LEFTALIGN |
Выравнивание по левой границе
относительно координаты, заданной параметром x |
TPM_RIGHTALIGN |
Выравнивание по правой границе
относительно координаты, заданной параметром x |
Дополнительно к перечисленным выше флагам вы можете указать один из двух флагов, определяющий клавишу мыши, предназначенную для выбора строки из плавающего меню:
Флаг |
Описание |
TPM_LEFTBUTTON |
Левая клавиша мыши |
TPM_RIGHTBUTTON |
Правая клавиша мыши |
Параметр nReserved зарезервирован, для совместимости со следующими версиями операционной системы Windows его значение должно быть равно 0.
Параметр hwnd задает идентификатор окна, которое получит сообщение WM_COMMAND после того как пользователь сделает выбор из плавающего меню. В операционной системе Windows версии 3.1 это сообщение попадает в функцию указанного окна после того как функция TrackPopupMenu возвратит управление. В версии 3.0 сообщение WM_COMMAND попадало в функцию окна до возврата управления функцией TrackPopupMenu.
Параметр lprc является указателем на структуру типа RECT, определяющую координаты прямоугольной области, в которой пользователь может выполнять выбор из меню. Если сделать щелчок мышью за пределами этой области, плавающее меню исчезнет с экрана. Такие действия эквивалентны отказу от выбора. Если задать для этого параметра значение NULL, размеры и расположение указанной выше прямоугольной области будут совпадать с размерами плавающего меню.
Как вы знаете, орган управления, созданный на базе предопределенного класса "edit", является простым редактором текста. В приложении SMARTPAD, которое будет описано немного позже, используется интересный прием, позволяющий вызвать на экран плавающее меню простым нажатием правой клавиши мыши внутри окна редактирования. Причем меню окажется как раз около курсора мыши, так что для работы с меню вам не придется передвигать мышь на большое расстояние.
Для редактора текста внутри операционной системы Windows определена функция окна, выполняющая всю работу по редактированию текста, выделению фрагментов текста, копирование выделенного фрагмента в универсальный буфер обмена Clipboard и т. д. Когда вы устанавливаете курсор мыши в окно редактирования и нажимаете правую клавишу мыши, сообщение WM_RBUTTONDOWN попадает в функцию окна редактора текста.
Однако функция родительского окна, создавшая редактор текста, получает только сообщение с кодом WM_COMMAND, да и то только при выполнении определенных операций с текстом. Поэтому сколько бы вы не нажимали правую кнопку мыши в окне редактора текста, родительское окно об этом никогда не узнает.
Как же нам быть? Ведь нам надо не только определить момент, в который пользователь нажал правую кнопку мыши, но и узнать текущие координаты курсора мыши, чтобы создать плавающее меню в нужном месте экрана (недалеко от курсора мыши).
Так как встроенная функция окна, используемая редактором текста, перехватывает сообщение WM_RBUTTONDOWN и "не выпускает" его наружу, нам надо вставить собственный обработчик сообщений перед стандартным для класса окна "edit".
Программный интерфейс Windows позволяет нам это сделать.
Определим в программе две переменные:
WNDPROC lpfnEditOldWndProc; WNDPROC lpfnEditWndProc;
Эти переменные будут использоваться для хранения, соответственно, указателя на старую функцию окна редактора текста и указателя на новую функцию окна редактора текста.
Для получения адреса функции окна редактора текста мы воспользуемся функцией GetWindowLong :
lpfnEditOldWndProc = (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC);
Если в качестве второго параметра этой функции передать константу GWL_WNDPROC , функция вернет адрес функции окна, идентификатор которого задан первым параметром. Возвращенное функцией GetWindowLong значение мы сохраним в переменной lpfnEditOldWndProc, так как наша функция окна, встроенная до стандартной, после выполнения своей задачи должна вызвать стандартную функцию окна (иначе редактор текста не будет работать).
Итак, адрес старой функции окна мы узнали. Теперь надо подготовить новую функцию окна, которая, если пользователь нажмет на правую клавишу мыши, будет выводить на экран плавающее меню. Вот эта функция:
// ====================================================== // Новая функция окна для редактора текста // ====================================================== LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Если в окне редактора текста пользователь нажал // правую клавишу мыши, выводим в позиции курсора мыши // плавающее меню if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt; // Преобразуем координаты курсора мыши в экранные pt = MAKEPOINT(lParam); ClientToScreen(hwnd, &pt); // Создаем пустое временное меню hmenuPopup = CreatePopupMenu(); // Заполняем временное меню AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save"); AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit"); // Выводим плавающее меню в позиции курсора мыши TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL); // Удаляем временное меню DestroyMenu(hmenuPopup); } // Вызываем старую функцию окна редактора текста return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam); }
Обратите внимание, что после завершения работы новая функция окна вызывает старую функцию окна. Так как ваше приложение не может вызывать функцию окна непосредственно, мы вызываем старую функцию окна при помощи функции CallWindowProc .
Таким образом, мы сделали то, что нам нужно - новая функция окна обрабатывает сообщение от правой клавиши мыши, выводит плавающее меню и затем вызывает стандартную функцию окна текстового редактора.
Однако для того чтобы вместо стандартной функции окна вызывалась наша, ее необходимо подключить при помощи функции SetWindowLong :
lpfnEditWndProc = (WNDPROC)MakeProcInstance((FARPROC)EditWndProc,hInst); SetWindowLong(hEdit, GWL_WNDPROC, (LONG)lpfnEditWndProc);
Перед вызовом функции мы создаем переходник и сохраняем его адрес в переменной lpfnEditWndProc.
Сразу после возвращения управления из функции SetWindowLong наша новая функция окна включается в работу, пропуская через себя все сообщения, предназначенные для стандартной функции окна редактора текста.
Описанная выше методика обычно используется в тех случаях, когда нужно изменить поведение стандартного органа управления или любого стандартного окна Windows с известным идентификатором (зная который можно "добраться" до функции окна).
Для ускорения доступа к строкам меню при помощи клавиатуры (а также для назначения тех или иных функций, не связанных с меню, комбинациям клавиш), используется так называемая таблица акселераторов (accelerator table ).
Таблица акселераторов находится в ресурсах приложения и определяет соответствие между комбинациями клавиш и значением параметра wParam сообщения WM_COMMAND, попадающего в функцию окна, когда вы нажимаете эти комбинации клавиш.
Например, вы можете определить, что комбинации клавиш <Control+Insert> соответствует значение wParam, равное CM_EDITCUT. В этом случае если нажать указанную выше комбинацию клавиш, в функцию окна попадет сообщение WM_COMMAND с параметром wParam, равным CM_EDITCUT.
Обычно комбинации клавиш, используемые для ускоренного выбора (или просто акселераторы) обозначаются в правом столбце меню (рис. 1.17).
Рис. 1.17. Акселераторы в меню "Edit"
Однако такое обозначение, сделанное при помощи символа \t в шаблоне меню не распознается Windows, а служит лишь для удобства пользователя. Для того чтобы комбинация клавиш стала работать как акселератор, она должна быть описана в таблице акселераторов. Кроме этого, приложение должно загрузить таблицу акселераторов из ресурсов приложения и изменить цикл обработки сообщений.
Таблица акселераторов определяется в файле описания ресурсов приложения в следующем виде:
<Id> ACCELERATORS BEGIN ....... ....... ....... END
Для ссылки на таблицу акселераторов используется идентификатор Id, который не должен совпадать с идентификаторами других ресурсов приложения, таких как строки, диалоги и т. д.
Между операторами BEGIN и END располагаются строки описания акселераторов. Они имеют следующий формат (в квадратных скобках указаны необязательные параметры):
Key, AccId, [KeyType[,]] [NOINVERT] [ALT] [SHIFT] [CONTROL]
Поле Key определяет клавишу, которая будет использована для создания акселератора. Вы можете использовать символ в коде ASCII, заключенный в двойные кавычки (например, "F"), комбинацию символа ASCII со знаком ^ (например, "^S", что соответствует комбинации клавиш <Control+S>), ASCII-код клавиши в виде целого значения, или виртуальный код клавиши (в символьном или цифровом виде).
Поле AccId соответствует значению параметра wParam сообщения WM_COMMAND, которое попадет в функцию окна при использовании акселератора.
Поле KeyTab может принимать значения ASCII или VIRTKEY. В первом случае поле Key определяет клавишу с использованием кода ASCII, во втором - с использованием кода виртуальной клавиши. По умолчанию используется значение ASCII.
Если указан параметр NOINVERT, при использовании акселератора соответствующая строка меню не выделяется. По умолчанию строка меню выделяется инвертированием цвета.
Если поле KeyTab содержит значение VIRTKEY, можно указывать параметры ALT, SHIFT или CONTROL. В этом случае для акселератора используется комбинация клавиши, указанной параметром Key, и клавиши ALT, SHIFT или CONTROL, соответственно.
Приведем пример описания таблицы акселераторов из приложения SMARTPAD:
APP_ACCELERATORS ACCELERATORS BEGIN "N", CM_FILENEW, VIRTKEY, CONTROL "S", CM_FILESAVE, VIRTKEY, CONTROL "O", CM_FILEOPEN, VIRTKEY, CONTROL "Z", CM_EDITUNDO, VIRTKEY, CONTROL "X", CM_EDITCUT, VIRTKEY, CONTROL "C", CM_EDITCOPY, VIRTKEY, CONTROL "V", CM_EDITPASTE, VIRTKEY, CONTROL VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL VK_F1, CM_HELPINDEX, VIRTKEY END
Здесь описана таблица акселераторов APP_ACCELERATORS, в которой определены девять акселераторов, т. е. девять комбинаций клавиш ускоренного выбора.
Для того чтобы акселератор, состоящий из комбинации символьной клавиши (такой, как "N") и клавиши <Control>, работал вне зависимости от состояния клавиши <Caps Lock>, мы использовали виртуальные коды. Если бы мы использовали коды ASCII, наш акселератор активизировался бы только при использовании заглавных букв (мы могли бы указать строчные буквы, например, "n", в этом случае для активизации акселератора следовало бы использовать строчные буквы).
Из-за того что клавиша <Caps Lock> может находиться в любом состоянии, лучше работать с виртуальными кодами клавиш, не зависящих от того, являются буквы строчными или прописными.
Напомним, что коды виртуальных клавиш описаны в файле windows.h.
Для загрузки таблицы акселераторов следует использовать функцию LoadAccelerators :
HACCEL WINAPI LoadAccelerators(HINSTANCE hInst, LPCSTR lpszTableName);
Параметр hInst определяет идентификатор копии приложения, из ресурсов которого будет загружена таблица акселераторов.
Параметр lpszTableName является указателем на строку, содержащую идентификатор таблицы акселераторов. Если для идентификации ресурса используется целое значение, оно должно быть преобразовано макрокомандой MAKEINTRESOURCE.
Функция LoadAccelerators возвращает идентификатор загруженной таблицы акселераторов или NULL при ошибке.
Загруженная таблица акселераторов автоматически уничтожается при завершении работы приложения.
Приложение SMARTPAD работает с таблицей акселераторов и загружает ее следующим образом:
haccel = LoadAccelerators(hInstance, "APP_ACCELERATORS");
Для использования акселераторов цикл обработки сообщений должен выглядеть следующим образом:
while(GetMessage(&msg, 0, 0, 0)) { if(!haccel || !TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
В этом фрагменте кода переменная haccel содержит идентификатор загруженной таблицы акселераторов. Если идентификатор не равен NULL, вызывается функция TranslateAccelerator . Эта функция ищет в очереди сообщений сообщения от клавиатуры, соответствующие определенным в ресурсах приложения акселераторам, преобразуя такие сообщения в сообщения WM_COMMAND и WM_SYSCOMMAND (если сообщение соответствует системному меню), передаваемые непосредственно в функцию окна, минуя очередь сообщений приложения .
Содержимое параметра wParam в последних двух сообщениях равно идентификатору, указанному в таблице акселераторов для данной комбинации клавиш.
Старшее слово параметр lParam содержит 1 для сообщений, которые пришли от акселераторов и 0 для сообщений, которые пришли от меню.
Приведем прототип функции TranslateAccelerator:
int WINAPI TranslateAccelerator(HWND hwnd, HACCEL haccel, MSG FAR* lpmsg);
Параметр hwnd определяет идентификатор окна, для которого выполняется преобразование клавиатурных сообщений.
Параметр haccel должен содержать идентификатор загруженной при помощи функции LoadAccelerators таблицы акселераторов.
Последний параметр lpmsg является указателем на структуру типа MSG, в которую должно быть записано обрабатываемое сообщение.
Если функция TranslateAccelerator выполнила преобразование сообщения, она возвращает ненулевое значение. В противном случае возвращается 0. Обработанное сообщение не следует передавать функциям TranslateMessage и DispatchMessage.
Только что вы узнали об акселераторах, используемых для упрощения работы с меню. В современных приложениях Windows широко используется еще один важный элемент пользовательского интерфейса, облегчающий работу с меню (и в некоторых случаях даже полностью заменяющий меню). Речь идет об органе управления, который часто называется Toolbar.
На рис. 1.18 показан Toolbar, расположенный под главным меню приложения Microsoft Word for Windows версии 2.0.
Рис. 1.18. Орган управления Toolbar в текстовом процессоре Microsoft Word for Windows версии 2.0
Toolbar с точки зрения пользователя представляет собой ни что иное, как набор кнопок с нарисованными на их поверхности пиктограммами. Каждая такая кнопка соответствует определенной строке в том или ином временном меню приложения. Например, самая левая кнопка на рис. 1.18 соответствует строке "New" из меню "File". Однако с кнопкой может быть связана и такая функция, для которой нет соответствия в меню приложения.
С точки зрения программиста орган управления Toolbar может представлять собой отдельный объект в виде дочернего окна с расположенными на нем кнопками или совокупность кнопок, созданных на поверхности главного окна приложения. Можно использовать и другие варианты построения Toolbar. К сожалению, в операционной системе Microsoft Windows версии 3.1 нет стандартного органа управления, способного выполнять функции Toolbar, поэтому программист должен создавать его самостоятельно.
Вы можете сделать Toolbar из стандартных кнопок, однако обычно используются кнопки, которые рисует родительское окно (т. е. имеющие стиль BS_OWNERDRAW).
В приложении SMARTPAD мы создали Toolbar как дочернее окно с расположенными на его поверхности кнопками стиля BS_OWNERDRAW. Когда пользователь нажимает на одну из кнопок, расположенных в окне Toolbar, в функцию родительского окна, создавшего Toolbar, приходит сообщение WM_COMMAND. Параметр wParam этого сообщения однозначно соответствует расположению кнопки в окне Toolbar.
Наш Toolbar создан как класс в терминах языка программирования C++. Вы можете изменять его, приспосабливая для ваших нужд, или определять на его основе новые классы. Например, можно сделать Toolbar, расположенный в окне приложения по вертикали, или создать для Toolbar отдельное перекрывающееся окно.
Для иллюстрации всего сказанного выше относительно использования меню и органа управления Toolbar мы приведем исходные тексты приложения SMARTPAD, которое реализует основные функции, связанные с редактированием текста (рис. 1.19).
Рис. 1.19. Главное окно приложения SMARTPAD
Так как мы пока еще не умеем работать с принтерами и шрифтами, первая версия нашего редактора текста несколько ограничена. Кроме того, пока не реализованы функции меню "Help". Тем не менее это приложение имеет Toolbar, использует акселераторы для доступа к основным функциям, модифицирует системное меню и по щелчку правой клавиши мыши создает в окне редактирования плавающее меню.
На примере этого приложения мы демонстрируем не только способы работы с различными типами меню, но и способ "перехвата" управления у функции окна органа управления класса "edit".
Орган управления Toolbar выделен в отдельный класс (в терминах языка программирования С++), исходные тексты которого находятся в двух файлах. Вы сможете с помощью этого класса без труда (почти) создавать в приложениях свой собственный Toolbar.
Основной файл приложения SMARTPAD приведен в листинге 1.8.
Листинг 1.8. Файл smartpad/smartpad.cpp
#define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> #include <string.h> #include <stdlib.h> #include "toolbar.hpp" #include "smartpad.hpp" // ====================================================== // Прототипы функций // ====================================================== BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg,WPARAM wParam,LPARAM lParam); HFILE OpenFile(void); HFILE OpenSaveFile(void); int SaveFileAs(HWND hwnd); int SaveFile(HWND hwnd); int SelectFile(HWND hwnd); // ====================================================== // Глобальные переменные // ====================================================== // Имя класса окна char const szClassName[] = "SmartPadAppClass"; // Заголовок окна char const szWindowTitle[] = "Smart Pad"; // Глобальная переменная для хранения идентификатора // текущей копии приложения HINSTANCE hInst; // Указатель на объект органа управления TOOLBAR Toolbar *Tb; // Переменные для хранения идентификаторов меню HMENU hmenuAppMenu; HMENU hmenuSystemMenu; // Идентификатор таблицы акселераторов HACCEL haccel; // Идентификатор редактора текста HWND hEdit; // Признак внесения изменений в текст BOOL bNeedSave; // Путь к редактируемому файлу char szCurrentFileName[128]; // Временный буфер char szTempBuffer[128]; // Признак запрета редактирования BOOL bReadOnly = FALSE; // Идентификаторы файлов HFILE hfSrcFile, hfDstFile; // Переменные для хранения адресов функций DLGPROC lpfnDlgProc; WNDPROC lpfnEditOldWndProc; WNDPROC lpfnEditWndProc; // Идентификатор главного окна HWND hwndMain; // ====================================================== // Функция WinMain // ====================================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Сохраняем идентификатор текущей копии приложения hInst = hInstance; // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Загружаем основное меню приложения hmenuAppMenu = LoadMenu(hInstance, "APP_MENU"); // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна hmenuAppMenu, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Сохраняем идентификатор главного окна в // глобальной переменной hwndMain = hwnd; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Создаем орган управления TOOLBAR Tb = new Toolbar(hInstance, hwnd, TB_FIRST); // Создаем кнопки в органе управления TOOLBAR Tb->InsertButton(0, MAKEINTRESOURCE(IDB_NEWUP), MAKEINTRESOURCE(IDB_NEWDOWN), MAKEINTRESOURCE(IDB_NEWUP)); Tb->InsertButton(1, MAKEINTRESOURCE(IDB_OPENUP), MAKEINTRESOURCE(IDB_OPENDOWN), MAKEINTRESOURCE(IDB_OPENGR)); Tb->InsertButton(2, MAKEINTRESOURCE(IDB_SAVEUP), MAKEINTRESOURCE(IDB_SAVEDOWN), MAKEINTRESOURCE(IDB_SAVEGR)); Tb->InsertButton(4, MAKEINTRESOURCE(IDB_CUTUP), MAKEINTRESOURCE(IDB_CUTDOWN), MAKEINTRESOURCE(IDB_CUTGR)); Tb->InsertButton(5, MAKEINTRESOURCE(IDB_COPYUP), MAKEINTRESOURCE(IDB_COPYDOWN), MAKEINTRESOURCE(IDB_COPYGR)); Tb->InsertButton(6, MAKEINTRESOURCE(IDB_PASTUP), MAKEINTRESOURCE(IDB_PASTDOWN), MAKEINTRESOURCE(IDB_PASTGR)); Tb->InsertButton(7, MAKEINTRESOURCE(IDB_UNDOUP), MAKEINTRESOURCE(IDB_UNDODOWN), MAKEINTRESOURCE(IDB_UNDOGR)); Tb->InsertButton(9, MAKEINTRESOURCE(IDB_EXITUP), MAKEINTRESOURCE(IDB_EXITDOWN), MAKEINTRESOURCE(IDB_EXITGR)); Tb->InsertButton(10, MAKEINTRESOURCE(IDB_HELPUP), MAKEINTRESOURCE(IDB_HELPDOWN), MAKEINTRESOURCE(IDB_HELPGR)); // Загружаем таблицу акселераторов haccel = LoadAccelerators(hInstance, "APP_ACCELERATORS"); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { if(!haccel || !TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } // ====================================================== // Функция InitApp // Выполняет регистрацию класса окна // ====================================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ====================================================== // Новая функция окна для редактора текста // ====================================================== LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Если в окне редактора текста пользователь нажал // правую клавишу мыши, выводим в позиции курсора мыши // плавающее меню if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt; // Преобразуем координаты курсора мыши в экранные pt = MAKEPOINT(lParam); ClientToScreen(hwnd, &pt); // Создаем пустое временное меню hmenuPopup = CreatePopupMenu(); // Заполняем временное меню AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save"); AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit"); // Выводим плавающее меню в позиции курсора мыши TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL); // Удаляем временное меню DestroyMenu(hmenuPopup); } // Вызываем старую функцию окна редактора текста return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam); } // ====================================================== // Функция главного окна приложения WndProc // ====================================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HFONT hfont; switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 30, 100, 100, hwnd, (HMENU) ID_EDIT, hInst, NULL); // Задаем для редактора текста шрифт с // переменной шириной символов hfont = GetStockFont(ANSI_VAR_FONT); SendMessage(hEdit, WM_SETFONT, (WPARAM)hfont, (LPARAM)MAKELONG((WORD)TRUE, 0)); // Создаем переходник для новой функции // окна редактора текста lpfnEditWndProc = (WNDPROC)MakeProcInstance((FARPROC)EditWndProc,hInst); // Определяем адрес старой функции // окна редактора текста lpfnEditOldWndProc = (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC); // Подключаем к редактору текста новую функцию окна SetWindowLong(hEdit, GWL_WNDPROC, (LONG)lpfnEditWndProc); // Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L); // Сбрасываем флаг обновления текста и флаг // запрета редактирования bNeedSave = FALSE; bReadOnly = FALSE; // Так как редактируемый файл не открывался и не // сохранялся, в переменную пути к нему записываем // пустую строку lstrcpy(szCurrentFileName, ""); // Устанавливаем заголовок окна приложения SetWindowText(hwnd, "SmartPad - [UNTITLED]"); // Определяем идентификатор системного меню hmenuSystemMenu = GetSystemMenu(hwnd, FALSE); // Добавляем в системное меню разделительную линию // и строку "About" AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0); AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED, CM_SYSABOUT, "&About..."); // Блокируем в системном меню строку "Close" EnableMenuItem(hmenuSystemMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); return 0; } case WM_SIZE: { // Устанавливаем новую ширину дочернего окна // органа управления TOOLBAR Tb->SetWidth(LOWORD(lParam)); // Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 26, LOWORD(lParam), HIWORD(lParam) - 26, TRUE); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; } // Это сообщение приходит от системного меню case WM_SYSCOMMAND: { // Необходимо замаскировать четыре младших бита // параметра wParam if((wParam & 0xfff0) == CM_SYSABOUT) { // Переходник для функции диалоговой панели lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst); // Создаем модальную диалоговую панель DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc); return 0; } // Блокируем строку "Close". Эта строка не является // обязательной, так как мы уже заблокировали эту // строку функцией EnableMenuItem else if((wParam & 0xfff0) == SC_CLOSE) return 0; break; } // Сообщение от меню и органа управления TOOLBAR case WM_COMMAND: { switch(wParam) { // Обработка извещений текстового редактора case ID_EDIT: { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } // Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bNeedSave = TRUE; } return 0; } // Эти строки меню пока заблокированы, так как // соответствующие функции не реализованы case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: { MessageBox(hwnd, "В текущей версии " "редактора SmartPad данная функция" " не реализована", NULL, MB_OK); return 0; } // На запрос подсказки выводим диалоговую панель // с информацией о программе case TB_HELP: case CM_HELPABOUT: { // Переходник для функции диалоговой панели lpfnDlgProc = (DLGPROC)MakeProcInstance( (FARPROC)DlgProc, hInst); // Создаем модальную диалоговую панель DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc); // Ликвидируем переходник FreeProcInstance((FARPROC) lpfnDlgProc); return 0; } // Переключение режима запрета редактирования case CM_EDITSETREADONLY: { // Если режим запрета редактирования выключен, // включаем его if(!bReadOnly) { // Запрещаем редактирование SendMessage(hEdit, EM_SETREADONLY, TRUE, 0L); // Отмечаем соответствующую строку в меню CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY, MF_BYCOMMAND | MF_CHECKED); // Устанавливаем флаг запрета редактирования bReadOnly = TRUE; } // Если режим запрета редактирования включен, // выключаем его else { SendMessage(hEdit, EM_SETREADONLY, FALSE, 0L); CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY, MF_BYCOMMAND | MF_UNCHECKED); bReadOnly = FALSE; } // Устанавливаем фокус ввода на редактор текста SetFocus(hEdit); return 0; } case CM_EDITPASTE: case TB_PAST: { SendMessage(hEdit, WM_PASTE, 0, 0L); SetFocus(hEdit); return 0; } case CM_EDITCOPY: case TB_COPY: { SendMessage(hEdit, WM_COPY, 0, 0L); SetFocus(hEdit); return 0; } case CM_EDITCUT: case TB_CUT: { SendMessage(hEdit, WM_CUT, 0, 0L); SetFocus(hEdit); return 0; } case CM_EDITCLEAR: { SendMessage(hEdit, WM_CLEAR, 0, 0L); SetFocus(hEdit); return 0; } case CM_EDITSELALL: { SendMessage(hEdit, EM_SETSEL, 0, MAKELPARAM(0, -1)); SetFocus(hEdit); return 0; } case CM_EDITUNDO: case TB_UNDO: { SendMessage(hEdit, EM_UNDO, 0, 0L); SetFocus(hEdit); return 0; } // Завершаем работу приложения case TB_EXIT: case CM_FILEEXIT: { // Проверяем флаг обновления if(bNeedSave) { // Если в тексте были изменения, // спрашиваем у пользователя, надо ли // сохранять текст в файле if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { // Если файл ни разу не сохранялся, // спрашиваем путь и имя нового файла if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd); // Изменяем заголовок главного окна // приложения в соответствии // с именем и путем к файлу wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } // Если файл уже сохранялся, записываем его // на прежнее место else SaveFile(hwnd); } } // Завершаем работу приложения DestroyWindow(hwnd); return 0; } case CM_FILENEW: case TB_NEW: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd); wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } else SaveFile(hwnd); } } // Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0"); bNeedSave = FALSE; lstrcpy(szCurrentFileName, ""); SetWindowText(hwnd, "SmartPad - [UNTITLED]"); SetFocus(hEdit); return 0; } case CM_FILEOPEN: case TB_OPEN: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) SaveFile(hwnd); } if(!SelectFile(hwnd)) { lstrcpy(szCurrentFileName, ""); SetWindowText(hwnd, "SmartPad - [UNTITLED]"); } else { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } return 0; } case CM_FILESAVEAS: { if(SaveFileAs(hwnd)) { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } else { lstrcpy(szCurrentFileName, ""); SetWindowText(hwnd, "SmartPad - [UNTITLED]"); } return 0; } case CM_FILESAVE: case TB_SAVE: { if(szCurrentFileName[0] == '\0') { if(SaveFileAs(hwnd)) { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } else { lstrcpy(szCurrentFileName, ""); SetWindowText(hwnd, "SmartPad - [UNTITLED]"); } } else SaveFile(hwnd); return 0; } default: break; } break; } // Это сообщение приходит при завершении работы // операционной системы Windows. Если мы не сохранили // редактируемый текст, спрашиваем у пользователя, // надо ли это делать case WM_QUERYENDSESSION: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd); wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName); SetWindowText(hwnd, szTempBuffer); } else SaveFile(hwnd); } } // Разрешаем операционной системе Windows // завершить свою работу return 1L; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ====================================================== // Функция OpenFile // Сохранение файла // ====================================================== HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn; // Буфер для записи пути к выбранному файлу char szFile[256]; // Буфер для записи имени выбранного файла char szFileTitle[256]; // Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; // Идентификатор открываемого файла HFILE hf; // Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0'; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ); // Сохраняем путь к открытому файлу lstrcpy(szCurrentFileName, ofn.lpstrFile); // Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; } // ====================================================== // Функция OpenSaveFile // Выбор файла для редактирования // ====================================================== HFILE OpenSaveFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); // Сохраняем путь к файлу lstrcpy(szCurrentFileName, ofn.lpstrFile); return hf; } else return 0; } // ====================================================== // Функция SaveFileAs // Сохранение текста в новом файле // ====================================================== int SaveFileAs(HWND hwnd) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Открываем выходной файл hfDstFile = OpenSaveFile(); if(!hfDstFile) return 0; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); // Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; } // Закрываем файл _lclose(hfDstFile); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); // Так как файл был только что сохранен, // сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit); return 1; } // ====================================================== // Функция SaveFile // Сохранение текста в старом файле // ====================================================== int SaveFile(HWND hwnd) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Открываем выходной файл hfDstFile = _lopen(szCurrentFileName, OF_WRITE); if(!hfDstFile) return 0; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); // Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; } // Закрываем файл _lclose(hfDstFile); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); // Так как файл был только что сохранен, // сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit); return 1; } // ====================================================== // Функция SelectFile // Загрузка текста из файла для редактирования // ====================================================== int SelectFile(HWND hwnd) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos; // Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0; // Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0); // Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; } // Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0; // Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize); // Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0'; // Закрываем файл _lclose(hfSrcFile); // Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer); // Освобождаем буфер free((void *)lpTextBuffer); // Сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit); return 1; } // ====================================================== // Функция DlgProc // ====================================================== #pragma argsused BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Инициализация диалоговой панели case WM_INITDIALOG: { return TRUE; } case WM_COMMAND: { switch(wParam) { // Сообщение от кнопки "OK" case IDOK: // Отмена диалоговой панели. // Это сообщение приходит, когда пользователь // нажимает на клавишу <Esc> case IDCANCEL: { // Устанавливаем флаг завершения диалога EndDialog(hdlg, 0); return TRUE; } } } } return FALSE; }
В области глобальных переменных среди прочих определений располагается определение указателя на класс Toolbar:
Toolbar *Tb;
Через этот указатель (разумеется, после создания объекта и инициализации указателя) будет выполняться одна из операций, определенных для Toolbar, - создание кнопки в заданной позиции.
Отметим также переменную bNeedSave типа BOOL, которая используется как признак необходимости сохранения редактируемого текста в файле.
В массиве szCurrentFileName[128] хранится путь к редактируемому файлу. Эта информация используется для формирования заголовка главного окна приложения и для сохранения файла при помощи строки "Save" из меню "File" или кнопки в Toolbar с изображением дискеты.
С помощью строки "Read Only" меню "Options" вы можете запретить редактирование текста. При этом в глобальную переменную bReadOnly типа BOOL записывается значение TRUE.
Три глобальные переменные используются для хранения, соответственно, адреса функции диалога, появляющегося при выборе строки "About..." (рис. 1.20), адреса стандартной функции диалога редактора текста и новой функции диалога, необходимой для вывода плавающего меню.
Рис. 1.20. Диалоговая панель "About" приложения SMARTPAD
Функция WinMain инициализирует приложение обычным образом и загружает основное меню из ресурсов приложения, вызывая функцию LoadMenu. Далее создается главное окно приложения, идентификатор которого сохраняется в глобальной переменной hwndMain.
После отображения главного окна создается орган управления Toolbar :
Tb = new Toolbar(hInstance, hwnd, TB_FIRST);
Конструктору объекта Toolbar передается идентификатор копии приложения hInstance, идентификатор окна, в котором необходимо расположить Toolbar и функция которого будет получать сообщение WM_COMMAND, и константу TB_FIRST, определяющую значение параметра wParam сообщения WM_COMMAND для самой левой кнопки в органе управления Toolbar. Для второй кнопки слева значение этого параметра будет равно TB_FIRST + 1, для третьей TB_FIRST + 2, и т. д.
Далее функция WinMain создает все необходимые кнопки, вызывая метод InsertButton, определенный в классе Toolbar:
Tb->InsertButton(0, MAKEINTRESOURCE(IDB_NEWUP), MAKEINTRESOURCE(IDB_NEWDOWN), MAKEINTRESOURCE(IDB_NEWUP)); Tb->InsertButton(1, MAKEINTRESOURCE(IDB_OPENUP), MAKEINTRESOURCE(IDB_OPENDOWN), MAKEINTRESOURCE(IDB_OPENGR)); Tb->InsertButton(2, MAKEINTRESOURCE(IDB_SAVEUP), MAKEINTRESOURCE(IDB_SAVEDOWN), MAKEINTRESOURCE(IDB_SAVEGR)); Tb->InsertButton(4, MAKEINTRESOURCE(IDB_CUTUP), MAKEINTRESOURCE(IDB_CUTDOWN), MAKEINTRESOURCE(IDB_CUTGR)); Tb->InsertButton(5, MAKEINTRESOURCE(IDB_COPYUP), MAKEINTRESOURCE(IDB_COPYDOWN), MAKEINTRESOURCE(IDB_COPYGR)); Tb->InsertButton(6, MAKEINTRESOURCE(IDB_PASTUP), MAKEINTRESOURCE(IDB_PASTDOWN), MAKEINTRESOURCE(IDB_PASTGR)); Tb->InsertButton(7, MAKEINTRESOURCE(IDB_UNDOUP), MAKEINTRESOURCE(IDB_UNDODOWN), MAKEINTRESOURCE(IDB_UNDOGR)); Tb->InsertButton(9, MAKEINTRESOURCE(IDB_EXITUP), MAKEINTRESOURCE(IDB_EXITDOWN), MAKEINTRESOURCE(IDB_EXITGR)); Tb->InsertButton(10, MAKEINTRESOURCE(IDB_HELPUP), MAKEINTRESOURCE(IDB_HELPDOWN), MAKEINTRESOURCE(IDB_HELPGR));
Первый параметр функции InsertButton определяет расположение кнопки в области, выделенной для ToolBar. Обратите внимание, что номера кнопок увеличиваются не монотонно. После кнопки с номером 2 следует кнопка с номером 4, а после кнопки с номером 7 следует кнопка с номером 9. Пропущенным номерам соответствуют пустые позиции в окне Toolbar.
Учтите, что параметр wParam сообщения WM_COMMAND, получаемого от Toolbar, зависит от номера позиции и от значения константы TB_FIRST, переданной конструктору при создании класса. Так как мы не создали кнопки с номерами 3 и 8, функция главного окна приложения никогда не получит сообщение WM_COMMAND с параметром wParam, равным TB_FIRST + 3 и TB_FIRST + 8.
Второй, третий и четвертый параметры функции InsertButton должны содержать идентификаторы изображений bitmap для кнопки в нормальном, нажатом и неактивном состоянии, соответственно.
После создания кнопок функция окна приложения будет получать от Toolbar сообщение WM_COMMAND.
Таким образом, использование класса Toolbar предельно просто. Вы должны создать объект класса Toolbar, указав идентификатор копии приложения, идентификатор главного окна приложения (или другого окна, которое будет содержать Toolbar). Затем вам надо вставить кнопки, вызвав несколько раз функцию InsertButton, которая является методом класса Toolbar. После этого орган управления Toolbar начинает работать, посылая в функцию окна сообщение WM_COMMAND.
Для установки ширины дочернего окна Toolbar определена функция SetWidth. Вы должны вызывать эту функцию в функции главного окна приложения при обработке сообщения WM_SIZE:
case WM_SIZE: { Tb->SetWidth(LOWORD(lParam)); ..... (другие строки) ...... return 0; }
Класс Toolbar определен в файле toolbar.hpp, поэтому в исходный текст программы необходимо включить следующую строку:
#include "toolbar.hpp"
После создания Toolbar функция WinMain загружает с помощью функции LoadAccelerators таблицу акселераторов, используемую для ускоренного доступа к строкам меню, после чего запускается цикл обработки сообщений. Для работы с акселераторами в цикле обработки сообщений вызывается функция TranslateAccelerators. Для получения символьных сообщений мы должны также вызывать в этом цикле функцию TranslateMessage.
Новая функция окна для редактора текста EditWndProc перехватывает сообщение WM_RBUTTONDOWN, возникающее в тот момент, когда пользователь нажимает правую кнопку мыши, и выводит плавающее меню.
Координаты курсора мыши передаются через параметр lParam. Эти координаты вычислены относительно верхнего левого угла внутренней области окна (client region). Так как функция TrackPopupMenu, создающая плавающее меню, использует экранные координаты, мы выполняем соответствующее преобразование с помощью функции ClientToScreen.
Если обработка сообщения от мыши завершена или пришло другое сообщение, предназначенное для функции окна редактора текста, новая функция окна EditWndProc передает сообщение без изменений стандартной функции окна через переходник lpfnEditOldWndProc:
return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam);
Теперь рассмотрим работу функции главного окна приложения, которая называется WndProc.
При создании главного окна приложения управление получает обработчик сообщения WM_CREATE. Он создает стандартный редактор текста на базе предопределенного класса окна "edit".
Для того чтобы придать редактируемому тексту более привлекательный вид, изменяем шрифт, используемый редактором текста на шрифт ANSI с переменной шириной букв:
hfont = GetStockFont (ANSI_VAR_FONT); SendMessage(hEdit, WM_SETFONT , (WPARAM)hfont, (LPARAM)MAKELONG((WORD)TRUE, 0));
Подробное описание этой операции и сообщения WM_SETFONT мы отложим до главы, посвященной шрифтам.
Далее обработчик сообщения WM_CREATE создает переходник для новой функции окна редактора текста, определяет адрес старой (т. е. стандартной) функции окна редактора текста и подключает новую функцию окна. Этот процесс был описан раньше.
После этого устанавливается максимальная длина редактируемого текста, инициализируются флаги, устанавливается новый заголовок главного окна приложения.
Хотя в этом нет никакой необходимости, приложение SMARTPAD изменяет системное меню. Мы сделали это исключительно для иллюстрации методов работы с системным меню.
Сначала с помощью функции GetSystemMenu мы определяем идентификатор системного меню. Затем в системное меню добавляется горизонтальная разделительная линия и строка "About...", при выборе которой на экране появляется диалоговая панель 'About". Для добавления используется функция AppendMenu.
Мы также блокируем строку "Close" в системном меню, для чего вызываем функцию EnableMenuItem. Напомним, что все стандартные строки системного меню имеют идентификаторы, для которых в файле windows.h определены символические константы. В честности, строка "Close" имеет идентификатор SC_CLOSE.
Обработчик сообщения WM_SIZE устанавливает новую ширину дочернего окна Toolbar и новый размер редактора текста:
{ Tb->SetWidth(LOWORD(lParam)); MoveWindow(hEdit, 0, 26, LOWORD(lParam), HIWORD(lParam) - 26, TRUE); return 0; }
Обработчик сообщения WM_FOCUS передает фокус окну редактора текста, вызывая функцию SetFocus.
Для обработки сообщений от системного меню в функции главного окна приложения предусмотрен обработчик сообщения WM_SYSCOMMAND. Мы уже рассказывали вам об особенности этого сообщения - перед сравнением младшие четыре бита параметра wParam необходимо замаскировать.
Обработчик сообщения WM_SYSCOMMAND реагирует на добавленную нами в системное меню строку "About..." с идентификатором CM_SYSABOUT. Дополнительно мы перехватываем сообщение от строки "Close" и "изымаем" его, блокируя работу соответствующей строки.
Обработчик сообщения WM_COMMAND получился достаточно сложным, так как наше приложение имеет большое меню и Toolbar. Все основные выполняемые функции объясняются в комментариях, поэтому мы для экономии места остановимся только на некоторых моментах.
Обработка извещений от редактора текста выполняется аналогично тому, как это сделано в приложении TEDIT, описанном в предыдущем томе "Библиотеки системного программиста".
При выборе строки "Read Only" из меню "Options" инвертируется содержимое флага запрета редактирования bReadOnly. Если этот флаг находился в состоянии FALSE, редактору текста посылается сообщение EM_SETREADONLY с параметром WParam, равном TRUE, после чего содержимое флага меняется на TRUE. После этого строка "Read Only" отмечается галочкой при помощи функции CheckMenuItem.
Если выбрать эту строку еще раз, запрет редактирования отменяется, флаг запрета редактирования bReadOnly снова инвертируется, вслед за чем удаляется отметка в строке меню.
Практически после выполнения всех операций, отнимающих фокус ввода у текстового редактора, мы возвращаем фокус редактору, вызывая функцию SetFocus.
Обратите внимание на обработчик сообщения WM_QUERYENDSESSION. Это сообщение передается приложению перед завершением работы операционной системы Windows. Приложение может запретить завершение работы Windows, вернув нулевое значение, или разрешить, вернув значение 1. Наше приложение в ответ на это сообщение проверяет состояние флага bNeedSave. Если текст не был сохранен, на экран выводится соответствующий запрос. Если в ответ на этот запрос вы нажмете клавишу "Yes", текст будет сохранен в файле. После этого обработчик сообщения WM_QUERYENDSESSION возвращает 1, разрешая операционной системе завершить свою работу.
Файл smartpad.hpp содержит определения всех используемых в файле smartpad.cpp символических констант (листинг 1.9).
Листинг 1.9. Файл smartpad/smartpad.hpp
// Идентификаторы пиктограмм #define IDB_NEWUP 300 #define IDB_NEWDOWN 301 #define IDB_NEWGR 302 #define IDB_OPENUP 303 #define IDB_OPENDOWN 304 #define IDB_OPENGR 305 #define IDB_SAVEUP 306 #define IDB_SAVEDOWN 307 #define IDB_SAVEGR 308 #define IDB_EXITUP 309 #define IDB_EXITDOWN 310 #define IDB_EXITGR 311 #define IDB_COPYUP 312 #define IDB_COPYDOWN 313 #define IDB_COPYGR 314 #define IDB_PASTUP 315 #define IDB_PASTDOWN 316 #define IDB_PASTGR 317 #define IDB_CUTUP 318 #define IDB_CUTDOWN 319 #define IDB_CUTGR 320 #define IDB_UNDOUP 321 #define IDB_UNDODOWN 322 #define IDB_UNDOGR 323 #define IDB_HELPUP 324 #define IDB_HELPDOWN 325 #define IDB_HELPGR 326 #define CM_FONT 24347 #define CM_SYSABOUT 0x8880 #define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341 #define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321 #define CM_EDITCLEAR 24320 #define CM_EDITSELALL 24319 #define CM_EDITSETREADONLY 24318 #define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331 /* Идентификатор редактора текста */ #define ID_EDIT 200 /* Идентификаторы кнопок TOOLBAR */ #define TB_FIRST 100 #define TB_NEW TB_FIRST + 0 #define TB_OPEN TB_FIRST + 1 #define TB_SAVE TB_FIRST + 2 #define TB_CUT TB_FIRST + 4 #define TB_COPY TB_FIRST + 5 #define TB_PAST TB_FIRST + 6 #define TB_UNDO TB_FIRST + 7 #define TB_EXIT TB_FIRST + 9 #define TB_HELP TB_FIRST + 10
Обратите внимание на описание идентификатора строки "About..." системного меню:
#define CM_SYSABOUT 0x8880
Значение этого идентификатора должно отличаться от значений других идентификаторов строк меню, причем младшие четыре бита не должны участвовать в сравнении.
В файле smartpad.rc (листинг 1.10) описаны ресурсы приложения.
Листинг 1.10. Файл smartpad/smartpad.rc
#include "smartpad.hpp" IDB_NEWUP BITMAP "newup.bmp" IDB_NEWDOWN BITMAP "newdown.bmp" IDB_NEWGR BITMAP "newgr.bmp" IDB_OPENUP BITMAP "openup.bmp" IDB_OPENDOWN BITMAP "opendown.bmp" IDB_OPENGR BITMAP "opengr.bmp" IDB_SAVEUP BITMAP "saveup.bmp" IDB_SAVEDOWN BITMAP "savedown.bmp" IDB_SAVEGR BITMAP "savegr.bmp" IDB_EXITUP BITMAP "exitup.bmp" IDB_EXITDOWN BITMAP "exitdown.bmp" IDB_EXITGR BITMAP "exitgr.bmp" IDB_COPYUP BITMAP "copyup.bmp" IDB_COPYDOWN BITMAP "copydown.bmp" IDB_COPYGR BITMAP "copygr.bmp" IDB_PASTUP BITMAP "pastup.bmp" IDB_PASTDOWN BITMAP "pastdown.bmp" IDB_PASTGR BITMAP "pastgr.bmp" IDB_CUTUP BITMAP "cutup.bmp" IDB_CUTDOWN BITMAP "cutdown.bmp" IDB_CUTGR BITMAP "cutgr.bmp" IDB_UNDOUP BITMAP "undoup.bmp" IDB_UNDODOWN BITMAP "undodown.bmp" IDB_UNDOGR BITMAP "undogr.bmp" IDB_HELPUP BITMAP "helpup.bmp" IDB_HELPDOWN BITMAP "helpdown.bmp" IDB_HELPGR BITMAP "helpgr.bmp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", CM_FILENEW MENUITEM "&Open...\tCtrl+O", CM_FILEOPEN MENUITEM "&Save\tCtrl+S", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT, GRAYED MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP, GRAYED MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP,GRAYED MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM SEPARATOR MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE MENUITEM "C&lear\tCtrl+Del", CM_EDITCLEAR MENUITEM SEPARATOR MENUITEM "Select &All", CM_EDITSELALL END POPUP "&Options" BEGIN MENUITEM "&Read-only", CM_EDITSETREADONLY MENUITEM SEPARATOR MENUITEM "&Set Font...", CM_FONT, GRAYED END POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, GRAYED MENUITEM "&Keyboard", CM_HELPKEYBOARD, GRAYED MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END APPICON ICON "appicon.ico" ABOUT DIALOG 25, 34, 118, 67 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" BEGIN CTEXT "Редактор текста\nSmart Pad\nVersion 1.0\n" "(C) Frolov A.V., 1994", -1, 34, 6, 79, 36, WS_CHILD | WS_VISIBLE | WS_GROUP ICON "APPICON", -1, 14, 16, 16, 16, WS_CHILD | WS_VISIBLE DEFPUSHBUTTON "OK", IDOK, 42, 47, 33, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP END APP_ACCELERATORS ACCELERATORS BEGIN "N", CM_FILENEW, VIRTKEY, CONTROL "S", CM_FILESAVE, VIRTKEY, CONTROL "O", CM_FILEOPEN, VIRTKEY, CONTROL "Z", CM_EDITUNDO, VIRTKEY, CONTROL "X", CM_EDITCUT, VIRTKEY, CONTROL "C", CM_EDITCOPY, VIRTKEY, CONTROL "V", CM_EDITPASTE, VIRTKEY, CONTROL VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL VK_F1, CM_HELPINDEX, VIRTKEY END
В файле описания ресурсов определена таблица акселераторов APP_ACCELERATORS. Эта таблица устанавливает соответствие между виртуальными кодами клавиш, используемых для ускоренного выбора функций из меню и соответствующие идентификаторы.
В листинге 1.11 изображены файлы *.bmp, на которые есть ссылки в файле описания ресурсов.
Листинг 1.11. Файлы smartpad/*.bmp
newup.bmp |
openup.bmp |
saveup.bmp |
cutup.bmp |
|
newdown.bmp |
opendown.bmp |
savedown.bmp |
cutdown.bmp |
|
newgr.bmp |
opengr.bmp |
savegr.bmp |
cutgr.bmp |
|
copyup.bmp |
pastup.bmp |
undoup.bmp |
exitup.bmp |
helpup.bmp |
copydown.bmp |
pastdown.bmp |
undodown.bmp |
exitdown.bmp |
helpdown.bmp |
copygr.bmp |
pastgr.bmp |
undogr.bmp |
exitgr.bmp |
helpgr.bmp |
Определение класса Toolbar, предназначенного для создания органа управления Toolbar, находится в файле toolbar.hpp (листинг 1.12).
Листинг 1.12. Файл smartpad/toolbar.hpp
#include <windows.h> #include <windowsx.h> #include <mem.h> // Прототип функции окна TOOLBAR LRESULT CALLBACK _export ToolbarWndProc(HWND, UINT, WPARAM, LPARAM); // ====================================================== // Определение класса TbMain // ====================================================== class TbMain { public: // Идентификатор приложения static HINSTANCE hInst; // Идентификаторы кнопок в различном состоянии static HBITMAP hbmpUp[20]; // исходное состояние static HBITMAP hbmpDown[20]; // нажатые static HBITMAP hbmpGrayed[20]; // заблокированные // Идентификатор родительского окна, создавшего TOOLBAR static HWND hwndParent; // Идентификатор первой кнопки в TOOLBAR static int nFirstId; }; // ====================================================== // Определение класса Toolbar // ====================================================== class Toolbar { RECT rcParent; // размеры родительского окна RECT rcToolbar; // размеры TOOLBAR ATOM aWndClass; // атом для кода возврата char szClassName[20]; // имя класса HWND hwndToolbar; // идентификатор окна TOOLBAR HWND hButton[20]; // идентификаторы кнопок int errno; // код ошибки public: // ====================================================== // Вычисление размеров окна TOOLBAR // ====================================================== void GetRect(void) { rcToolbar.left = GetRectLeft(); rcToolbar.top = GetRectTop(); rcToolbar.right = GetRectRihgt(); rcToolbar.bottom = GetRectBottom(); } // ====================================================== // Регистрация класса для окна TOOLBAR // ====================================================== virtual BOOL RegisterWndClass(void) { WNDCLASS wc; // структура для регистрации класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) ToolbarWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = TbMain::hInst; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszMenuName = (LPSTR)NULL; // Определяем кисть для закрашивания окна wc.hbrBackground = GetBrush(); // Имя класса lstrcpy(szClassName, "FrolovAVToolBar"); wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ====================================================== // Создание дочернего окна, которое будет использоваться // для размещения кнопок TOOLBAR // ====================================================== void CreateTbWindow(void) { hwndToolbar = CreateWindow( szClassName, // имя класса окна NULL, // заголовок окна WS_CHILDWINDOW | WS_VISIBLE, // стиль окна rcToolbar.left, // указываем расположение rcToolbar.top, // и размеры окна rcToolbar.right, rcToolbar.bottom, TbMain::hwndParent, // идентификатор родительского окна 0, // идентификатор меню TbMain::hInst, // идентификатор приложения NULL); // указатель на дополнительные параметры } // ====================================================== // Устанавливаем ширину окна TOOLBAR // ====================================================== void SetWidth(int nWidth) { RECT rcOld; GetClientRect(hwndToolbar, &rcOld); MoveWindow(hwndToolbar, rcOld.left, rcOld.top, nWidth, rcOld.bottom, TRUE); } // ====================================================== // Вставляем кнопку в TOOLBAR // ====================================================== BOOL InsertButton(UINT nPosition, LPCSTR lpszBmpUp, LPCSTR lpszBmpDown, LPCSTR lpszBmpGrayed) { // Загружаем указанные в параметрах функции изображения TbMain::hbmpUp[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpUp); TbMain::hbmpDown[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpDown); TbMain::hbmpGrayed[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpGrayed); // Создаем орган управления - кнопку, которую // рисует родительское окно. В нашем случае это будет // окно TOOLBAR hButton[nPosition] = CreateWindow("button", NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, GetBtnX(nPosition), // определяем расположение кнопки GetBtnY(nPosition), // исходя из ее номера GetBmpWidth(), // ширина кнопки GetBmpHeigt(), // высота кнопки hwndToolbar, // родительское окно для кнопки // Идентификатор кнопки (HMENU)(nPosition + TbMain::nFirstId), TbMain::hInst, NULL); return TRUE; } // ====================================================== // Конструктор для класса Toolbar // ====================================================== Toolbar(HINSTANCE hInst, HWND hwndParent, int nFirstid); // ====================================================== // Деструктор для класса Toolbar // ====================================================== ~Toolbar(); // Проверка признака ошибки int Error(void) { return errno; } // Определение координат окна TOOLBAR virtual int GetRectLeft(void) { return 0; } virtual int GetRectTop(void) { return 0; } virtual int GetRectRihgt(void) { return rcParent.right; } virtual int GetRectBottom(void) { return 26; } // Определение кисти для окна TOOLBAR virtual HBRUSH GetBrush(void) { return GetStockBrush(LTGRAY_BRUSH); } // Определение расположения кнопки исходя из ее номера virtual int GetBtnX(int nPosition) { return 3 + (nPosition * 30); } virtual int GetBtnY(int nPosition) { return 3; } // Определение размеров кнопки virtual int GetBmpWidth(void) { return 30; } virtual int GetBmpHeigt(void) { return 20; } };
В этом файле определен также класс TbMain, все члены которого описаны как статические. Такой класс можно использовать вместо набора глобальных переменных, что улучшает структуру программы.
В классе TbMain хранится идентификатор приложения hInst, три массива, в которых хранятся идентификаторы изображений bitmap для кнопок в исходном (hbmpUp), нажатом (hbmpDown) и заблокированном (hbmpGrayed) состоянии, идентификатор родительского окна, на поверхности которого создается Toolbar, идентификатор самой левой кнопки в окне Toolbar (nFirstId). Все перечисленные выше переменные должны быть доступны для методов класса Toolbar и для функции окна Toolbar.
В классе Toolbar определены переменные, предназначенные для хранения размеров родительского окна и окна Toolbar, имени класса Toolbar, идентификаторов кнопок, кода ошибки.
Метод GetRect предназначен для определения размеров дочернего окна Toolbar, на поверхности которого создаются кнопки. Этот метод вызывает функции GetRectLeft, GetRectTop, GetRectRight, GetRectBottom, определяющие соответственно расположение левой, верхней, правой и нижней границ дочернего окна.
Вы можете создать свой класс как дочерний для класса Toolbar и переопределить все или некоторые из перечисленных функций, создав, например, вертикальный Toolbar.
Метод RegisterWndClass регистрирует класс для дочернего окна Toolbar. Этот метод использует функцию GetBrush для определения кисти, используемой для закраски поверхности дочернего окна. В классе Toolbar используется светло-серая кисть. Создавая собственный класс на базе класса Toolbar вы можете переопределить функцию GetBrush и закрасить поверхность дочернего окна в любой цвет.
Метод CreateTbWindow создает дочернее окно Toolbar. Перед вызовом этого метода необходимо с помощью метода GetRect определить размеры и расположение дочернего окна органа управления Toolbar.
Для того чтобы при изменении размеров основного окна обеспечить соответствующее изменение размеров окна Toolbar, в классе Toolbar определен метод SetWidth. Единственный параметр nWidth должен содержать значение новой ширины окна. Метод SetWidth необходимо вызывать из функции главного окна приложения (или другого окна, на поверхности которого расположен Toolbar) при обработке сообщения WM_SIZE.
Если вы создаете вертикальный Toolbar, вы можете определить в своем классе, созданном на базе класса Toolbar, функцию SetHeight, изменяющую высоту дочернего окна Toolbar.
Для вставки в Toolbar кнопки вы должны вызвать метод InsertButton.
Параметр NPosition определяет расположение кнопки на поверхности Toolbar и идентификатор кнопки, передаваемый в родительское окно через параметр wParam сообщения WM_COMMAND. Идентификатор кнопки зависит от расположения кнопки в окне Toolbar и определяется при помощи следующего выражения:
nPosition + TbMain::nFirstId
Таким образом, самой левой кнопке в горизонтальном органе управления Toolbar соответствует идентификатор TbMain::nFirstId, значение которого вы задаете при вызове конструктора класса Toolbar:
Toolbar(HINSTANCE hInst, HWND hwndParent, int nFirstid);
Дополнительно вы должны указать конструктору идентификатор текущей копии приложения hInst и идентификатор родительского окна, на поверхности которого расположен Toolbar. Определение конструктора и деструктора класса Toolbar находится в файле toolbar.cpp (листинг 1.13).
Среди других методов, определенных в классе Toolbar, отметим метод GetBtnX. Этот метод используется для вычисления X-координаты кнопки в окне Toolbar в зависимости от ее номера. Если вы создаете свой Toolbar на базе класса Toolbar, вы можете использовать другой алгоритм размещения кнопок, переопределив эту функцию, а также функцию GetBtnY, предназначенную для вычисления Y-координаты кнопки.
По умолчанию для кнопок используются изображения bitmap размером 30 х 20 пикселов. Если вам нужны кнопки другого размера, переопределите методы GetBmpWidth и GetBmpHeight.
В файле toolbar.cpp (листинг 1.13) находятся определения некоторых методов класса Toolbar, членов класса TbMain и функции дочернего окна Toolbar.
Листинг 1.13. Файл smartpad/toolbar.cpp
#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h> #include "toolbar.hpp" LRESULT CALLBACK _export ToolbarWndProc(HWND, UINT, WPARAM, LPARAM); void DrawButton(LPDRAWITEMSTRUCT lpInfo); void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap); // Определяем члены статического класса TbMain HBITMAP TbMain::hbmpUp[20]; HBITMAP TbMain::hbmpDown[20]; HBITMAP TbMain::hbmpGrayed[20]; HWND TbMain::hwndParent; HINSTANCE TbMain::hInst; int TbMain::nFirstId = 0; // ====================================================== // Конструктор класса Toolbar // ====================================================== Toolbar::Toolbar(HINSTANCE hInstance, HWND hwndParentWindow, int nFirstId) { // Сбрасываем признак ошибки errno = 0; // Сохраняем идентификатор копии приложения, // идентификатор родительского окна, создавшего // TOOLBAR, и идентификатор первой кнопки в // органе управления TOOLBAR TbMain::hInst = hInstance; TbMain::hwndParent = hwndParentWindow; TbMain::nFirstId = nFirstId; // Определяем размеры внутренней области // родительского окна GetClientRect(TbMain::hwndParent, &rcParent); // Определяем размеры дочернего окна, которое // будет использовано для создания органа TOOLBAR GetRect(); // Регистрируем класс для дочернего окна TOOLBAR if(!RegisterWndClass()) { errno = 1; return; } // Создаем дочернее окно TOOLBAR CreateTbWindow(); if(hwndToolbar == NULL) { errno = 2; return; } } // ====================================================== // Деструктор класса Toolbar // ====================================================== Toolbar::~Toolbar() { // Уничтожаем дочернее окно TOOLBAR DestroyWindow(hwndToolbar); } // ====================================================== // Функция окна ToolbarWndProc для дочернего окна TOOLBAR // ====================================================== LRESULT CALLBACK _export ToolbarWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // Передаем сообщение WM_COMMAND от кнопок в // родительское окно case WM_COMMAND: { SendMessage(TbMain::hwndParent, WM_COMMAND, wParam, lParam); return 0; } // Это сообщение приходит при изменении состояния // дочернего окна органа управления, когда окно // нужно перерисовать case WM_DRAWITEM: { // Перерисовываем кнопку DrawButton( (LPDRAWITEMSTRUCT)lParam ); break; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ====================================================== // Функция DrawButton // Перерисовывает кнопку // ====================================================== void DrawButton(LPDRAWITEMSTRUCT lpInfo) { HBITMAP hbm; // Обрабатываем сообщение WM_DRAWITEM // только если оно поступило от кнопки if(lpInfo->CtlType != ODT_BUTTON) return; hbm = TbMain::hbmpUp[(lpInfo->CtlID) - TbMain::nFirstId]; // Если кнопка выбрана, рисуем ее в нажатом // состоянии if (lpInfo->itemState & ODS_SELECTED) { hbm = TbMain::hbmpDown[(lpInfo->CtlID) - TbMain::nFirstId]; } // Если кнопка неактивна, загружаем идентификатор // изображения кнопки в неактивном состоянии else if (lpInfo->itemState & ODS_DISABLED) { hbm = TbMain::hbmpGrayed[(lpInfo->CtlID) - TbMain::nFirstId]; } // При ошибке ничего не рисуем if(!hbm) return; // Если кнопка выбрана и ее надо целиком // перерисовать, вызываем функцию DrawBitmap if((lpInfo->itemAction & ODA_DRAWENTIRE) || (lpInfo->itemAction & ODA_SELECT)) { // Рисуем кнопку DrawBitmap(lpInfo->hDC, (lpInfo->rcItem).left, (lpInfo->rcItem).top , hbm); } }
Конструктор класса Toolbar сбрасывает признак ошибки errno, инициализирует члены класса TbMain, определяет размеры внутренней области родительского окна, размеры дочернего окна, регистрирует класс дочернего окна и создает само дочернее окно.
Работа деструктор заключается в уничтожении дочернего окна, для чего используется функция DestroyWindow.
Функция дочернего окна ToolbarWndProc обрабатывает сообщение WM_COMMAND, поступающее от кнопок, и формирует это же сообщение для дочернего окна. Кроме того, эта функция обеспечивает работу кнопок, для чего в ней предусмотрены обработчик сообщения WM_DRAWITEM. Этот обработчик вызывает функцию DrawButton, предназначенную для рисования кнопок.
Функция DrawButton рисует кнопку в нужном виде, выбирая идентификаторы соответствующих изображений bitmap из массивов, определенных в классе TbMain. Для рисования изображения вызывается функция DrawBitmap (листинг 1.14). Мы уже пользовались этой функцией.
Листинг 1.14. Файл smartpad/drawbmp.cpp
// ====================================================== // Рисование изображения типа bitmap // ====================================================== #define STRICT #include <windows.h> void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg; // Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC); // Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap); // Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC)); // Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm); ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота // Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1); ptOrg.x = 0; ptOrg.y = 0; // Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1); // Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY); // Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); } // Удаляем контекст памяти DeleteDC(hMemDC); }
Файл определения модуля для приложения SMARTPAD приведен в листинге 1.15.
Листинг 1.15. Файл smartpad/smartpad.def
; ============================= ; Файл определения модуля ; ============================= NAME SMARTPAD DESCRIPTION 'Приложение SMARTPAD, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
До сих пор наши меню состояли только из текстовых строк и разделительных линий, однако вы можете сделать меню из произвольных графических изображений. Если вам не нравится стандартная отметка строк меню при помощи галочки, ее можно заменить на любое графическое изображение (небольшого размера).
Для того чтобы вместо строк в меню расположить графические изображения bitmap, эти изображения надо загрузить из ресурсов или создать другим способом, а затем идентификатор изображения указать в качестве последнего параметра функций AppendMenu или InsertMenu. Необходимо также использовать флаг MF_BITMAP :
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE1, (LPCSTR)(DWORD)hbmpLine1);
Если вы загрузили bitmap при помощи функции LoadBitmap, не забудьте перед завершением приложения (или после удаления соответствующего меню) удалить bitmap функцией DeleteObject.
Для замены стандартного символа отметки строки меню (галочки) на другой предназначена функция SetMenuItemBitmaps:
BOOL WINAPI SetMenuItemBitmaps(HMENU hmenu, UINT idItem, UINT fuFlags, HBITMAP hbmUnckecked, HBITMAP hbmChecked);
Эта функция выполняет замену символа отметки для строки idItem меню hmenu.
Для параметра fuFlags можно использовать два значения - MF_BYCOMMAND и MF_BYPOSITION . Если указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор элемента меню, для которого выполняется замена символа отметки. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер элемента меню.
Параметр hbmUnckecked представляет собой идентификатор изображения, которое будет расположено слева от неотмеченной строки меню, параметр hbmChecked - идентификатор изображения символа отметки.
Любой из последних параметров или оба можно указывать как NULL. В этом случае будет использовано изображение по умолчанию (т. е. слева от неотмеченной строки не будет никакого изображения, слева от отмеченной - галочка).
Однако есть небольшая тонкость. Вы не можете использовать для отметки строк меню изображения bitmap любого размера. Нужные размеры изображения необходимо определить при помощи функции GetMenuCheckMarkDimensions :
DWORD WINAPI GetMenuCheckMarkDimensions(void);
Младшее слово возвращаемого значения содержит ширину изображения, старшее - высоту:
DWORD dwMark; WORD wWidth, wHeight; dwMark = GetMenuCheckMarkDimensions(); wWidth = LOWORD(dwMark); wHeight = HIWORD(dwMark);
Тонкость заключается в том, что на момент трансляции исходного текста приложения вы не можете знать требуемые размеры изображения. Так как на этапе сборки загрузочного модуля приложения размеры изображения неизвестны, вы (строго говоря) не можете просто загрузить соответствующие изображения bitmap из ресурсов приложения.
Выход заключается в том, чтобы в процессе инициализации приложения определить требуемые размеры изображения, вызвав функцию GetMenuCheckMarkDimensions, а затем подготовить нужные изображения bitmap в памяти. Однако, так как мы еще не рассказывали вам подробно об изображениях bitmap, в примере GMENU, приведенном в следующем разделе, мы для простоты (данная глава посвящена меню, а не изображениям bitmap) все-таки загрузили изображения размером 10 х 10 пикселов из ресурсов.
При создании строки меню вы можете указать константу MF_OWNERDRAW. В этом случае функция окна, работающая с данным меню, должна будет сама нарисовать строку меню. Можно нарисовать любое изображение.
Перед тем как отобразить меню, содержащее строки со стилем MF_OWNERDRAW , операционная система Windows посылает в функцию окна сообщение WM_MEASUREITEM . В ответ на это сообщение функция должна сообщить Windows размеры окна, необходимые для изображения строки меню.
Когда надо отобразить строку меню, Windows посылает в родительское окно сообщение WM_DRAWITEM . Вместе с этим сообщением передается вся информация, необходимая родительскому окну для того чтобы нарисовать строку меню.
Параметр lParam сообщения WM_MEASUREITEM содержит указатель на структуру MEASUREITEMSTRUCT , описанную в файле windows.h:
typedef struct tagMEASUREITEMSTRUCT { UINT CtlType; UINT CtlID; UINT itemID; UINT itemWidth; UINT itemHeight; DWORD itemData; } MEASUREITEMSTRUCT;
В этом же файле описаны ближний и дальний указатели на эту структуру:
typedef MEASUREITEMSTRUCT NEAR* PMEASUREITEMSTRUCT; typedef MEASUREITEMSTRUCT FAR* LPMEASUREITEMSTRUCT;
Когда функция окна получает сообщение WM_MEASUREITEM, поле CtlType содержит значение ODT_MENU, в поле itemID находится идентификатор строки меню, а в поле itemData - 32-разрядное значение, переданное через параметр lpNewItem функций AppendMenu, InsetMenu, ModifyMenu. Поле CtlID не используется.
Получив сообщение WM_MEASUREITEM, функция окна должна, пользуясь значением указателя из lParam, записать в поле itemWidth ширину строки меню, а в поле itemHeight - высоту строки меню.
Параметр lParam сообщения WM_DRAWITEM содержит указатель на структуру DRAWITEMSTRUCT . Эта структура и соответствующие указатели описаны в файле windows.h следующим образом:
typedef struct tagDRAWITEMSTRUCT { UINT CtlType; UINT CtlID; UINT itemID; UINT itemAction; UINT itemState; HWND hwndItem; HDC hDC; RECT rcItem; DWORD itemData; } DRAWITEMSTRUCT; typedef DRAWITEMSTRUCT NEAR* PDRAWITEMSTRUCT; typedef DRAWITEMSTRUCT FAR* LPDRAWITEMSTRUCT;
Приведем назначение отдельных полей структуры DRAWITEMSTRUCT при ее использовании для меню.
Имя поля |
Описание |
CtlType |
Тип органа управления. Для меню
принимает значение ODT_MENU |
CtlID |
Идентификатор органа управления. Для
меню не используется |
itemID |
Идентификатор строки меню |
itemAction |
Действия, которые необходимо выполнить
при изображении строки меню. Определен в виде
отдельных битовых флагов: |
itemState |
Вид, в котором необходимо изобразить
строку меню. Определен в виде отдельных битовых
флагов: |
hwndItem |
Идентификатор меню |
hDC |
Контекст устройства, который необходимо
использовать для рисования строки меню |
rcItem |
Прямоугольные границы, внутри которых
необходимо нарисовать строку |
itemData |
Содержит 32-битовое значение, полученное
через параметр lpNewItem функций AppendMenu, InsetMenu, ModifyMenu |
В заключение первой главы, посвященной меню, приведем приложение GMENU, которое демонстрирует использование графики в меню. Меню верхнего уровня приложения GMENU содержит временное меню "LineStyle", которое используется для выбора одного из стилей линии (рис. 1.21).
Рис. 1.21. Графические изображения bitmap в строках меню
В данном случае использование графики из-за большей наглядности предпочтительнее текстового описания внешнего вида линий, такого как "тонкая линия", "толстая линия", пунктирная линия" и "волнистая линия".
Мы также создали собственный символ для отметки строки меню в виде закрашенного кружка (рис. 1.22).
Рис. 1.22. Отметка строки меню при помощи графического изображения bitmap
Главный файл приложения GMENU приведен в листинге 1.16.
Листинг 1.16. Файл gmenu/gmenu.cpp
// ---------------------------------------------- // Использование графических изображений в меню // ---------------------------------------------- #define STRICT #include <windows.h> #include <mem.h> #include "gmenu.hpp" // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "GMenuClass"; // Заголовок окна char const szWindowTitle[] = "Menu Demo"; // Идентификатор меню верхнего уровня HMENU hmenu; // Идентификаторы временных меню HMENU hmenuFile; // "File" HMENU hmenuLineStyle; // "Edit" HMENU hmenuHelp; // "Help" // Идентификаторы графических изображений для строк меню HBITMAP hbmpLine1; HBITMAP hbmpLine2; HBITMAP hbmpLine3; HBITMAP hbmpLine4; HBITMAP hbmpLineStyle; HBITMAP hbmpChecked; HBITMAP hbmpUnchecked; // Идентификатор текущей копии приложения HINSTANCE hInst; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор текущей копии приложения hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // Загружаем изображения строк меню hbmpLine1 = LoadBitmap(hInst, "LINE1"); hbmpLine2 = LoadBitmap(hInst, "LINE2"); hbmpLine3 = LoadBitmap(hInst, "LINE3"); hbmpLine4 = LoadBitmap(hInst, "LINE4"); hbmpLineStyle = LoadBitmap(hInst, "LINESTYLE"); hbmpChecked = LoadBitmap(hInst, "CHECKED"); hbmpUnchecked = LoadBitmap(hInst, "UNCHECKED"); // Создаем пустое меню верхнего уровня hmenu = CreateMenu(); // Подключаем меню к главному окну приложения SetMenu(hwnd, hmenu); // Создаем временные меню hmenuFile = CreatePopupMenu(); hmenuHelp = CreatePopupMenu(); hmenuLineStyle = CreatePopupMenu(); // Добавляем строки к меню "File" AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILECLOSE, "&Close"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILESAVE, "&Save"); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILESAVEAS, "Save &as..."); AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuFile, MF_DISABLED | MF_STRING, CM_FILEDEMO, "&Demo Version"); AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEEXIT, "E&xit"); // Для строки "Demo Version" меню "File" определяем // изображения, которые будут использоваться для // вывода строки в отмеченном и неотмеченном состоянии SetMenuItemBitmaps(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND, hbmpUnchecked, hbmpChecked); // Отмечаем строку "Demo Version" CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED); // Добавляем строки к меню "Line Style". // Вместо текстовых строк используем графические // изображения bitmap AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE1, (LPCSTR)(DWORD)hbmpLine1); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE2, (LPCSTR)(DWORD)hbmpLine2); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE3, (LPCSTR)(DWORD)hbmpLine3); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE4, (LPCSTR)(DWORD)hbmpLine4); // Добавляем строки к меню "Help" AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPINDEX, "&Index\tF1"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPKEYBOARD, "&Keyboard"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPCOMMANDS, "&Commands"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPPROCEDURES, "&Procedures"); AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPUSING_HELP, "&Using help"); AppendMenu(hmenuHelp, MF_SEPARATOR, 0, NULL); AppendMenu(hmenuHelp, MF_ENABLED | MF_STRING, CM_HELPABOUT, "&About..."); // Добавляем временные меню к меню верхнего уровня AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File"); // Для временного меню "Line Style" используем // изображение bitmap AppendMenu(hmenu, MF_ENABLED | MF_POPUP | MF_BITMAP, (UINT)hmenuLineStyle, (LPCSTR)(DWORD)hbmpLineStyle); AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help"); // Перерисовываем меню DrawMenuBar(hwnd); return 0; } case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_FILESAVEAS: case CM_FILESAVE: case CM_FILEOPEN: case CM_FILENEW: case CM_FILECLOSE: { // Выводим сообщение об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK); return 0; } // Выбрали строку "About..." в меню "Help" case CM_HELPABOUT: { MessageBox(hwnd, "Приложение GMENU\n(C) Фролов А.В., 1994", szWindowTitle, MB_OK | MB_ICONINFORMATION); return 0; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } case WM_DESTROY: { // Уничтожаем созданные ранее меню DestroyMenu(hmenuFile); DestroyMenu(hmenuHelp); DestroyMenu(hmenuLineStyle); DestroyMenu(hmenu); // Удаляем изображения DeleteObject(hbmpLine1); DeleteObject(hbmpLine2); DeleteObject(hbmpLine3); DeleteObject(hbmpLine4); DeleteObject(hbmpLineStyle); DeleteObject(hbmpChecked); DeleteObject(hbmpUnchecked); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
При обработке сообщения WM_CREATE приложение, наряду с другими инициализирующими действиями, загружает из ресурсов приложения все необходимые для меню изображения bitmap:
hbmpLine1 = LoadBitmap(hInst, "LINE1"); hbmpLine2 = LoadBitmap(hInst, "LINE2"); hbmpLine3 = LoadBitmap(hInst, "LINE3"); hbmpLine4 = LoadBitmap(hInst, "LINE4"); hbmpLineStyle = LoadBitmap(hInst, "LINESTYLE"); hbmpChecked = LoadBitmap(hInst, "CHECKED"); hbmpUnchecked = LoadBitmap(hInst, "UNCHECKED");
Для строки "Demo Version" мы используем созданные нами и описанные в ресурсах приложения изображения bitmap, для чего вызываем функцию SetMenuItemBitmaps :
SetMenuItemBitmaps(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND, hbmpUnchecked, hbmpChecked);
Далее мы отмечаем указанную строку:
CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
Затем мы переходим к формированию временного меню, содержащего графические изображения. Для этого в меню hmenuLineStyle, созданное ранее как пустое, мы добавляем четыре строки:
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE1, (LPCSTR)(DWORD)hbmpLine1); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE2, (LPCSTR)(DWORD)hbmpLine2); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE3, (LPCSTR)(DWORD)hbmpLine3); AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE4, (LPCSTR)(DWORD)hbmpLine4);
Как видно из рис. 1.21, для строки "Line Style" используется нестандартный шрифт. Точнее говоря, для этой строки мы использовали изображение bitmap, на котором написаны слова "Line Style":
AppendMenu(hmenu, MF_ENABLED | MF_POPUP | MF_BITMAP, (UINT)hmenuLineStyle, (LPCSTR)(DWORD)hbmpLineStyle);
Перед завершением работы приложения мы удаляем все загруженные изображения bitmap для освобождения системных ресурсов:
DeleteObject(hbmpLine1); DeleteObject(hbmpLine2); DeleteObject(hbmpLine3); DeleteObject(hbmpLine4); DeleteObject(hbmpLineStyle); DeleteObject(hbmpChecked); DeleteObject(hbmpUnchecked);
Символические константы, использованные в приложении GMENU, описаны в файле gmenu.cpp (листинг 1.17).
Листинг 1.17. Файл gmenu/gmenu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341 #define CM_FILEEXIT 24338 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331 #define CM_FILECLOSE 24330 #define CM_FILEDEMO 24329 #define CM_LINE1 100 #define CM_LINE2 101 #define CM_LINE3 102 #define CM_LINE4 103
Файл описания ресурсов приложения GMENU приведен в листинге 1.18. В нем находятся ссылки на изображения bitmap, используемые для отображения строк меню.
Листинг 1.18. Файл gmenu/gmenu.rc
LINE1 BITMAP "line1.bmp" LINE2 BITMAP "line2.bmp" LINE3 BITMAP "line3.bmp" LINE4 BITMAP "line4.bmp" CHECKED BITMAP "checked.bmp" UNCHECKED BITMAP "uncheck.bmp" LINESTYLE BITMAP "linestyl.bmp"
В листинге 1.19 приведен внешний вид этих изображений.
Листинг 1.19. Файлы gmenu/*.bmp
|
linestyl.bmp |
|
line2.bmp |
|
line3.bmp |
|
line3.bmp |
|
line4.bmp |
|
checked.bmp |
|
uncheck.bmp |
Файл определения модуля приложения GMENU приведен в листинге 1.20.
Листинг 1.20. Файл gmenu/gmenu.def
; ============================= ; Файл определения модуля ; ============================= NAME GMENU DESCRIPTION 'Приложение GMENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple