Win32 API. Меню

ОГЛАВЛЕНИЕ

Меню (menu) состоит из перечня пунктов меню (menu items). Выбор пункта меню открывает подменю или принуждает прикладную программу выполнить команду. Этот краткий обзор описывает меню и объясняет, как использовать их в прикладных программах, разработанных при помощи Microsoft Win32 прикладного программного интерфейса (API).

Строки меню и меню

Меню выстраивается иерархически. На верхнем уровне иерархии стоит строка горизонтальное меню (menu bar); вертикальные или просто меню (menus) выскакивают вниз из строки меню, а в нижних уровнях – подменю (submenus). Строка меню иногда называется меню верхнего уровня (top-level menu), а меню и подменю также известны как всплывающие меню (pop-up menus).

Пункт меню может или выполнить команду или открыть подменю. Пункт, который выполняет команду, называется командным пунктом (command item) или командой (command).

Пункт в горизонтальном меню почти всегда открывает меню. Строки меню редко содержат командные пункты. Меню, открытое из строки меню выскакивает вниз из нее и иногда называется выскакивающее меню (drop-down menu). Когда выскакивающее меню показывается на экране, оно связано со строкой меню. Пункт меню в строке меню, который открывает выскакивающее меню, называется также именем меню (menu name).

Имена меню в строке меню представляют основные категории команд, которые обеспечивает прикладная программа. Выбор имени меню из строки меню обычно открывает меню, пункты которого соответствуют командам в данной категории. Например, строка меню может содержать имя меню Файл (File) которое, когда выбрано пользователем, активизирует меню с пунктами такими как Создать (New), Открыть (Open) и Сохранить (Save).

Только перекрывающее или выскакивающее окно может содержать строку меню; дочернее окно не может содержать её. Если окно имеет область заголовка, Windows устанавливает строку меню прямо под ней. Строка меню всегда видима. Подменю не видимо, но только до тех пор, пока пользователь не выберет пункт меню, который активизирует его. Для получения дополнительной информации о перекрывающих и выскакивающих окнах, см. Общие стили окна.

Каждое меню должно иметь окно владельца. Windows отправляет сообщения окну владельцу меню, когда пользователь выбирает меню или избирает пункт из меню. Эти сообщения описаны в разделе Сообщения используемые меню.


 

Вспомогательное меню

Windows к тому же предоставляет и вспомогательное меню (shortcut menus). Вспомогательное меню не связано со строкой меню; оно может появляться где угодно на экране. Прикладная программа обычно связывает вспомогательное меню с частями окна, такими как рабочая область или с конкретным объектом, типа пиктограммы. По этой причине, эти меню также называются "контекстные меню".

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


 

Меню окна

Меню окна (window menu) (известное также как Системное меню (System menu или Control menu)) - всплывающее меню, определяемое и управляемое почти исключительно операционной системой. Пользователь может открыть меню окна щелчком по пиктограмме программы в области заголовка или щелкая правой кнопкой мыши где-нибудь в области заголовка.

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

Когда пользователь выбирает команду из меню окна, Windows отправляет сообщение WM_SYSCOMMAND окну владельцу меню. В большинстве прикладных программ, оконная процедура не обрабатывает сообщения из меню окна. Вместо этого, она просто посылает сообщения в функцию DefWindowProc для обработки сообщения системой по умолчанию. Если прикладная программа добавляет команду в меню окна, оконная процедура должна обрабатывать эту команду.

Прикладная программа, которая модифицирует меню окна, может использовать функцию GetSystemMenu, чтобы создать копию заданного по умолчанию меню окна. Любое окно, которое не использует функцию GetSystemMenu, чтобы делать свою собственную копию меню окна, получает стандартное меню окна. 


 

Идентификатор справки

С каждой строкой меню, меню, подменю и вспомогательным меню связан 32-разрядный идентификатор справки. Если пользователь нажимает клавишу F1, в то время, когда меню активно, это значение оправляется окну владельцу как часть сообщения WM_HELP. Для получения дополнительной информации о идентификаторах справки, см. главу Справка. 

Дескрипторы меню

Система создает уникальный дескриптор для каждого меню. Дескриптор меню (menu handle) является значением типа HMENU. Прикладная программа должна определять дескриптор меню во многих из функций меню Windows. Вы получаете дескриптор строки меню, когда создаете меню или загружаете ресурс меню. Для получения дополнительной информации о создании и загрузке меню, см. Создание меню.

Чтобы извлечь данные о дескрипторе строки меню для меню, которое было создано или загружено, используется функция GetMenu. Чтобы извлечь данные о дескрипторе подменю, связанного с пунктом меню, используйте функцию GetSubMenu или GetMenuItemInfo. Чтобы извлечь данные о дескрипторе меню окна, используйте функцию GetSystemMenu


 

Пункты меню

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

Командные пункты и пункты, которые открывают подменю

Когда пользователь выбирает командный пункт, Windows отправляет сообщение о команде окну, которое владеет меню. Если командный пункт находится в меню окна, Windows отправляет сообщение WM_SYSCOMMAND. Иначе, она посылает сообщение WM_COMMAND.

Дескриптор соответствующего подменю связан с каждым пунктом меню, который его открывает. Когда пользователь указывает на такой пункт, Windows открывает подменю. Окну владельцу никакого сообщения о команде не посылается. Однако Windows отправляет сообщение WM_INITMENUPOPUP окну владельцу меню перед показом на экране подменю. Вы можете получить дескриптор подменю, связанного с пунктом меню при помощи использования функции GetSubMenu или GetMenuItemInfo.

Строка меню обычно содержит имена меню, но она может содержать также и командные пункты. Подменю обычно содержит командные пункты, но оно может содержать также и пункты, которые открывают вложенные подменю. Добавляя такие пункты в подменю, Вы можете вкладывать меню на любую глубину. Чтобы обеспечить пользователя визуальным сигналом, Windows автоматически показывает на экране маленькую стрелку справа от текста пункта меню, который открывает подменю.

Идентификатор пункта меню.

Уникальное, определяемое программой целое число, связанное с каждым пунктом меню, называется идентификатором пункта меню (menu-item identifier). Когда пользователь выбирает командный пункт из меню, Windows отправляет идентификатор пункта окну владельцу как часть сообщения WM_COMMAND. Оконная процедура проверяет идентификатор, чтобы установить источник сообщения и обрабатывает сообщение соответственно. Кроме того, Вы можете определить пункт меню, используя его идентификатор, когда вызываете функции меню; например, чтобы включить или отключить пункт меню.

Идентификатор пункта меню должен быть значением от 0 до 65,535, даже притом, что это - 32-разрядное целое число. Это так потому, что сообщение WM_COMMAND посылает идентификатор пункта меню как младшее слово параметра wParam.

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

Чтобы извлечь данные об идентификаторе пункта меню в заданной позиции, используйте функцию GetMenuItemID или GetMenuItemInfo.

Позиция пункта меню

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

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

Заданные по умолчанию пункты меню

Подменю может содержать один заданный по умолчанию пункт меню. Когда пользователь открывает подменю, дважды щелкая мышью, Windows отправляет сообщение о команде окну владельцу меню и закрывает меню, как будто был выбран заданный по умолчанию командный пункт. Если нет никакого заданного по умолчанию командного пункта, подменю остается открытым. Чтобы извлечь данные и установить заданный по умолчанию пункт для подменю, используйте функции GetMenuDefaultItem и SetMenuDefaultItem

Пункты меню с галочкой и без галочки

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

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

Атрибут метки "галочкой " проверяет, отмечен ли пункт меню "галочкой". Вы можете установить атрибут отметки "галочкой " пункта меню при помощи использования функции CheckMenuItem. Вы можете использовать функцию GetMenuState, чтобы установить, отмечен или не отмечен в настоящее время пункт меню "галочкой".

Вместо CheckMenuItem и GetMenuState, Вы можете использовать функции GetMenuItemInfo и SetMenuItemInfo, чтобы извлекать данные и устанавливать наличие отметки "галочкой" в пункте меню.

Иногда, группа пунктов меню соответствует набору взаимоисключающих параметров. В этом случае, Вы можете обозначать выбранный параметр при помощи использования отметки пункта меню "радиоселектором" ("радиоселектор" аналогичен органу управления "радио-кнопке"). Отметки пунктов меню "радиоселектором" отображаются точечным рисунком маркера вместо точечного рисунка отметки "галочкой". Чтобы отметить пункт меню и сделать его пунктом с "радиоселектором", используйте функцию CheckMenuRadioItem.

По умолчанию, Windows показывает на экране отметку "галочкой" или точечный рисунок "радиоселектора" рядом с отмеченными пунктами меню и без точечного рисунка рядом со снятыми отметками "галочкой" пунктами меню. Тем не менее, Вы можете использовать функцию SetMenuItemBitmaps, чтобы ассоциировать определяемые программой точечные рисунки отметки и снятие отметки "галочкой" с пунктом меню. Windows затем использует заданные точечные рисунки, чтобы указать состояние пункта меню с "галочкой" или без отметки "галочкой".

Определяемые программой точечные рисунки, связанные с пунктом меню, должны быть того же самого размера, как и заданный по умолчанию точечный рисунок "галочки", габариты которого могут изменяться в зависимости от разрешающей способности экрана. Чтобы извлечь данные о правильных размерах, используйте функцию GetMenuCheckMarkDimensions. Вы можете создавать многокомпонентные растровые ресурсы для различных разрешений экрана; создайте один растровый ресурс, и, в случае необходимости, масштабируйте его; или создайте точечный рисунок во время выполнения программы и рисуйте изображение в нем. Точечные рисунки могут быть или одноцветные или цветные. Однако, поскольку пункты меню, когда выделяются, инвертируются, внешний вид некоторых инвертированных цветных точечных рисунков может быть не самый лучший. Для получения дополнительной информации, см. Точечные рисунки.

Включенные, недоступные и заблокированные пункты меню

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

Когда пункты меню не доступны пользователю, они должны быть серыми или заблокированными. Недоступные и заблокированные пункты меню не могут быть выбраны. Заблокированный пункт выглядит точно так же как включенный пункт. Когда пользователь нажимает на заблокированный пункт, он не выбирается и ничего не происходит. Заблокированные пункты могут быть полезны, например, в руководстве, показывающем меню, которое выглядит активным, но (в учебных целях) не является таковым.

Прикладная программа окрашивает в серый цвет недоступный пункт меню, чтобы обеспечить визуальный сигнал пользователю, что команда не доступна. Вы можете использовать недоступный пункт, когда действие не соответствующе (например, Вы можете окрашивать в серый цвет команду Печать (Print) в меню Файл (File), когда в системе принтер не установлен).

Функция EnableMenuItem включает, окрашенный в серый цвет или отключенный пункт меню. Чтобы установить, каким является пункт меню включенным, недоступным или заблокированным, используйте функцию GetMenuItemInfo.

Вместо GetMenuItemInfo, Вы можете использовать также и функцию GetMenuState, чтобы установить включен, недоступен или заблокирован пункт меню.

Выделенные пункты меню

Windows автоматически выделяет пункты меню, когда пользователь выбирает их. Тем не менее, выделение может быть явно добавлено или удалено с имени пункта в строке меню при помощи использования функция HiliteMenuItem. Эта функция не имеет никакого влияния на пункты в меню. Когда HiliteMenuItem используется, чтобы выделить название меню, несмотря на это, имя появляется только тогда, когда оно выбрано. Если пользователь нажимает клавишу ENTER, подсвеченный пункт не выбирается. Эта функция может быть полезной, например, в прикладной программе подготовки, которая показывает использование меню. 

Нарисованные пользователем (собственные) пункты меню

Прикладная программа может полностью управлять внешним видом пункта меню при помощи использования нарисованного пользователем (собственного) пункта (owner-drawn item). Нарисованные пользователем пункты требуют, чтобы приложение брало всю ответственность на себя за состояние прорисовки выбора (выделения), "галочки" и снятие отметки "галочкой". Например, если прикладная программа предоставляет меню шрифта, она может прописывать каждый пункт меню при помощи использования соответствующего шрифта; пункт для Латинского шрифта будет прописан латинским, пункт для Курсива будет прописан курсивом и так далее.

Для получения дополнительной информации, см. Создание нарисованных пользователем пунктов меню. 

Разделители пунктов меню и переносы строк

Windows предоставляет специальный тип пункта меню, называемый разделителем (separator), который появляется в виде горизонтальной линии. Вы можете использовать разделитель, чтобы поделить меню на группы родственных пунктов. Разделитель не может быть использован в строке меню, а пользователь не может выбрать разделитель.

Когда строка меню содержит большее количество имен пунктов меню, чем может разместить на одной строке, Windows переносит по словам строку меню, автоматически разрывая её на две или несколько строк. Вы можете заставить сделать перенос строки в конкретном пункте горизонтального меню, присвоив пункту флажок типа MFT_MENUBREAK. Windows размещает этот пункт и все последующие пункты на новой строке.

Когда меню содержит большее количество пунктов, чем может поместиться в одном столбце, Windows автоматически разрывает меню на два или несколько столбцов. Вы можете заставить сделать разрыв столбца в конкретном пункте в меню, присваивая пункту флажок типа MFT_MENUBREAK. Windows размещает этот пункт и все последующие пункты в новом столбце. Флажок типа MFT_MENUBARBREAK имеет то же самое влияние, за исключением того, что между новым и старым столбцом появляется вертикальная линия.

Если Вы используете функции AppendMenu, InsertMenu или ModifyMenu, чтобы назначить переносы строк, Вы должны присвоить типовые флажки MF_MENUBREAK или MF_MENUBARBREAK.

 

Доступ к меню с клавиатуры

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

Стандартный (общепринятый) интерфейс клавиатуры

Windows разработана так, чтобы работать с мышью, или без нее, или другим устройством управления позицией. Поэтому Windows предоставляет стандартный (общепринятый) интерфейс клавиатуры, благодаря которому пользователь может использовать клавиатуру, чтобы выбрать пункты меню. Этот интерфейс клавиатуры не нуждается в специальном коде. Прикладная программа получает командное сообщение, выбирает ли пользователь пункт меню через клавиатуру или при помощи использования мышь. Стандартный интерфейс клавиатуры обрабатывает следующие нажатия клавиши:

  • Буквенный символ - Выбирает первый пункт меню с заданным символом как клавиша доступа. Если выбранный пункт вызывает меню, меню отображается, а первый пункт выделяется. Иначе, пункт меню выбирается.
  • ALT - Переключает в режим или из режима строки меню.
  • ALT+SPACEBAR - Показывает на экране меню окна.
  • ENTER - Активизирует меню и выбирает его первый пункт, если пункт связан с этим меню. Иначе, это нажатие клавиши выбирает пункт, как будто пользователь отпустил кнопку мыши, в то время когда пункт был выбран.
  • ESC - Выход из режима меню.
  • LEFT ARROW (стрелка влево) - Циклически передвигается к предыдущему пункту меню верхнего уровня. Пункты меню верхнего уровня включают в себя имена меню и меню окна. Если выбранный пункт находится в меню, выбирается предыдущий столбец в меню или предыдущий пункт меню верхнего уровня.
  • RIGHT ARROW (стрелка вправо) - Работает подобно клавише СТРЕЛКА ВЛЕВО, за исключением того, что действует в противоположном направлении. В меню, это нажатие клавиши перемещает вперед на один столбец; когда текущий выбранный пункт находится в крайне правом столбце, выбранным является следующее меню.
  • UP или DOWN ARROWS (стрелки вверх или вниз) - Активизирует меню, когда нажимается на имени меню. Когда нажимается в меню, нажатие клавиши СТРЕЛКА ВВЕРХ выбирает предыдущий пункт; нажатие клавиши СТРЕЛКА ВНИЗ выбирает следующий.

Клавиши доступа к меню

Стандартный интерфейс клавиатуры для меню может быть расширен, при помощи добавления клавиш доступа (мнемонических) к пунктам меню. Клавиша доступа - подчеркнутый символ в тексте пункта меню. Когда меню активно, пользователь может выбрать пункт меню, нажимая клавишу, которая соответствует подчеркнутому символу пункта. Пользователь делает строку меню активной, нажимая клавишу ALT, чтобы выделить первый пункт в горизонтальном меню. Меню активизируется, когда оно показывается на экране.

Чтобы создать клавишу доступа для пункта меню, пред любым символом в текстовой строке пункта проставьте амперсанд. Например, текстовая строка "&Move" принуждает Windows подчеркнуть символ "M".

Быстрые клавиши меню

В дополнение к имеемым клавишам доступа, пункт меню может иметь быструю клавишу, связанную с ним. Быстрая клавиша(сочетание клавиш) отличается от клавиши доступа тем, что меню, чтобы сработать по быстрой клавише, не должно быть активным. К тому же, клавиша доступа всегда связана с пунктом меню, в то время как быстрая клавиша - обычно (но не обязательно) связана с пунктом меню.

Текст, который идентифицирует быструю клавишу, добавляется в текстовую строку пункта меню. Текст клавишной комбинации быстрого вызова появляется справа от названия пункта меню, после наклонной черты влево (обратного слэша) и символа табуляции (\t). Например, "&Close\tAlt+F4" представляет команду Закрыть (Close) с комбинацией клавиш ALT+F4 как быстрой клавиши и с символом "C" как клавишей доступа. Для получения дополнительной информации, см. Ускорители клавиатуры.


 

Создание меню

Вы можете создать меню, используя или шаблон меню или функции его создания. Шаблоны меню обычно определяются как ресурсы. Ресурсы шаблона меню могут быть загружены явно или назначены как заданное по умолчанию меню для класса окна. Вы можете также создавать ресурсы шаблона меню в памяти динамически.

Ресурсы шаблона меню

Большинство прикладных программ создает меню, используя ресурсы шаблона меню. Шаблон меню (menu template) определяет меню, включая в себя пункты в строке меню и всех меню. За информацией о создании ресурса шаблона меню, см. документацию, включенную в ваши инструментальные средства разработки.

После того, как Вы создадите ресурс шаблона меню и добавите его к исполняемому (.EXE) файлу вашего приложения, Вы можете использовать функцию LoadMenu, чтобы загрузить этот ресурс в память. Эта функция возвращает значение дескриптора меню, который Вы можете затем присваивать окну при помощи использования функции SetMenu.

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


Шаблон меню в памяти

Меню может быть создано из шаблона меню, который формируется в памяти во время выполнения программы. Например, прикладная программа, которая позволяет пользователю настраивать свое меню, может создать шаблон меню в памяти, основанной на стандартных установках пользователя. Приложение может затем сохранить шаблон в файле или в системном реестре для будущего использования. Чтобы создать меню из шаблона в памяти, используйте функцию LoadMenuIndirect. За описаниями форматов шаблона меню, обратись к статье Использование ресурса шаблона меню.

Стандартный шаблон меню состоит из структуры MENUITEMTEMPLATEHEADER, сопровождаемой одной или несколькими структурами MENUITEMTEMPLATE.

Расширенный шаблон меню состоит из структуры MENUEX_TEMPLATE_HEADER, сопровождаемой одной или несколькими структурами MENUEX_TEMPLATE_ITEM.


 

Функции создания меню

Используя функции создания меню, Вы можете создавать меню во время выполнения программы или добавлять пункты меню к существующим меню. Вы можете использовать функцию CreateMenu, чтобы создать пустую строку меню и функцию CreatePopupMenu, чтобы создать пустое меню. Чтобы добавлять пункты к меню, используйте функцию InsertMenuItem. Устаревшие функции AppendMenu и InsertMenu все еще поддерживаются, но для новых прикладных программ должна быть использована функция InsertMenuItem.

Показ меню на экране

После того, как меню было загружено или создано, оно должно быть связано с окном до того, как Windows сможет показать его на экране. Вы можете предназначить меню, при помощи определения меню класса. Для получения дополнительной информации о меню класса, см. Меню класса окна. Вы можете также предназначить меню для окна, при помощи определения дескриптора меню как параметра hMenu функции CreateWindow или CreateWindowEx или путем вызова функции SetMenu.

Чтобы показать на экране вспомогательное меню используют функцию TrackPopupMenuEx. Вспомогательное меню, также называемое плавающим, выскакивающим или контекстным меню, обычно показывается на экране, когда обрабатывается сообщение WM_CONTEXTMENU.

Устаревшая функция TrackPopupMenu все еще поддерживается, но новые прикладные программы должны использовать функцию TrackPopupMenuEx.

Меню класса окна

Вы можете определить заданное по умолчанию меню, называемое меню класса (class menu), когда регистрируете класс окна. Чтобы сделать так, присвойте имя ресурса шаблона меню элементу lpszMenuName структуры WNDCLASS, используемой для регистрации класса.

По умолчанию, каждому окну назначается меню класса для класса окна, так что вам не нужно явно загружать меню и присваивать его каждому окну. Вы можете заменять меню класса, при помощи определения другого дескриптора меню при вызове функции CreateWindowEx. Вы можете также изменить меню окна после того, как оно создано при помощи использования функция SetMenu. Для получения дополнительной информации, см. Классы окна.

Разрушение Меню

Если меню связано с окном, а это окно разрушено, Windows автоматически уничтожает меню, освобождая дескриптор меню и память, занятую меню. Windows не делает, автоматического уничтожения меню, которое не связано с окном. Прикладная программа должна уничтожить отмененное предназначение меню путем вызова функция DestroyMenu. Иначе, меню продолжает существовать в памяти даже после того, как приложение закрывается.


 

Сообщения, используемые меню

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

Когда пользователь активизирует пункт в горизонтальном меню, окно владелец сначала получает сообщение WM_SYSCOMMAND. Это сообщение включает в себя флажок, который указывает, активизировал ли пользователь меню при помощи использования клавиатуры (SC_KEYMENU) или мыши (SC_MOUSEMENU). Для получения дополнительной информации об интерфейсе клавиатуры для меню, см. Доступ к меню через клавиатуру.

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

Когда пользователь указывает на пункт меню, который открывает подменю, Windows, отправляет окну владельцу сообщение WM_INITMENUPOPUP перед отображением на экране подменю. Это сообщение дает прикладной программе возможность изменить подменю прежде, чем оно покажется на экране.

Каждый раз, когда пользователь перемещает выделение от одного пункта меню к другому, Windows отправляет сообщение WM_MENUSELECT оконной процедуре окна владельца меню. Это сообщение идентифицирует текущий выбранный пункт меню. Многие из прикладных программ предоставляют информационную область внизу своих основных окон и используют это сообщение, чтобы показать на экране дополнительную информацию о выбранном пункте меню.

Когда пользователь выбирает командный пункт из меню, Windows отправляет оконной процедуре сообщение WM_COMMAND. Младшее слово параметра wParam сообщения WM_COMMAND содержит идентификатор выбранного пункта. Оконная процедура должна проверить идентификатор и обработать сообщение соответственно.

Не все меню являются доступными через строку меню окна. Многие из прикладных программ показывают на экране вспомогательное меню, когда пользователь щелкает мышью по правой кнопке мыши в конкретном месте. Такие прикладные программы, если это им свойственно, должны обрабатывать сообщение WM_CONTEXTMENU и показывать на экране вспомогательное меню. Если прикладная программа не показывает на экране вспомогательное меню, она должна передать сообщение WM_CONTEXTMENU функции DefWindowProc для обработки по умолчанию.


 

Изменения меню

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

Чтобы добавить пункт меню, используйте функцию InsertMenuItem. Вы можете использовать функцию SetMenuItemInfo, чтобы изменить атрибуты существующего пункта меню. Параметр lpmii указывает на структуру MENUITEMINFO, которая содержит новые атрибуты и определяет, какие атрибуты изменяются. Атрибуты пункта меню включают в себя тип, состояние, идентификатор, подменю, точечные рисунки, данные пункта и текст.

Устаревшие функции AppendMenu и InsertMenu могут быть также использованы, чтобы добавить пункты меню, но новые прикладные программы должны использовать InsertMenuItem. Функция AppendMenu добавляет в конец пункт меню или подменю; функция InsertMenu вставляет пункт меню в заданной позиции в меню или подменю. Обе функции дают возможность атрибутам пункта меню быть определяемыми, включая в себя в любом случае, включение, блокирование, недоступность, отметку "галочкой или снятие отметки "галочкой" пункта меню.

Чтобы изменить внешний вид или атрибуты существующего пункта меню, используйте функцию ModifyMenu. Например, текстовая строка или точечный рисунок пункта меню могут быть включены, заблокированы, недоступны, отмечены "галочкой" или со снятой "галочкой". Функция ModifyMenu заменяет определяемый пункт меню на новый пункт.

Чтобы извлечь информацию о пункте меню, используйте функцию GetMenuItemInfo. Параметр lpmii указывает на структуру MENUITEMINFO, которая определяет извлекаемые атрибуты и принимает их текущие значения.

Чтобы удалить пункт меню из меню, используйте функцию DeleteMenu или RemoveMenu. Если удаляемый пункт тот, который открывает подменю, DeleteMenu удаляет связанное подменю, сбрасывая дескриптор меню и освобождая память, использованную подменю. Функция RemoveMenu удаляет пункт меню, но если пункт открывает подменю, функция не уничтожает подменю или дескриптор, позволяя подменю многократно использоваться.

Чтобы перерисовывать строку меню, после того как строка меню была изменена, используйте функцию DrawMenuBar. Иначе, изменения не появятся до тех пор, пока Windows не перерисует окно владельца.


 

Использование меню

Использование ресурса шаблона меню
Создание вспомогательного (контекстного) меню
Использование точечных рисунков пунктов меню
Создание нарисованных пользователем пунктов меню
Использование пользовательских точечных рисунков "галочки "

Использование ресурса шаблона меню

Вы обычно включаете меню в прикладную программу, при помощи создания ресурса шаблона меню и затем загружаете меню во время прогона программы. Этот раздел описывает формат шаблона меню и объясняет, как загрузить ресурс шаблона меню и использовать его в вашей прикладной программе. Информацию о создании ресурса шаблона меню, см. документацию, включенную с вашими инструментами разработки программ.

Расширенный формат шаблона меню

Расширенный формат шаблона меню поддерживает функциональные возможности меню, выполненные для версий Windows 95 и Windows NT 4.0. Подобно ресурсам шаблона меню, использованным с более ранними версиями Windows, расширенные ресурсы шаблона меню имеют тип ресурса RT_MENU. Windows различает два формата ресурса по номеру версии, который является первым элементом заголовка ресурса.

Расширенный шаблон меню состоит из структуры MENUEX_TEMPLATE_HEADER, сопровождаемой еще одной структурой MENUEX_TEMPLATE_ITEM определяющей пункты.

Старый формат шаблона меню

Старый шаблон меню (для версий Windows ранее, чем Windows 95 и Windows NT 4.0) задает меню, но не поддерживает его новые функциональные возможности. Старый ресурс шаблона меню имеет тип ресурса RT_MENU.

Старый шаблон меню состоит из структуры MENUITEMTEMPLATEHEADER, сопровождаемой одним или большим количеством структур MENUITEMTEMPLATE.

Загрузка ресурса шаблона меню

Чтобы загрузить ресурс шаблона меню, используйте функцию LoadMenu, определяя дескриптор модуля, который содержит ресурс и идентификатор шаблона меню. Функция LoadMenu возвращает дескриптор меню, который Вы можете использовать для привязки меню к окну. Это окно становится окном владельцем меню, принимая все сообщения, созданные меню.

Чтобы создать меню из шаблона меню, который находится уже в памяти, используйте функцию LoadMenuIndirect. Это полезно тогда, если ваша прикладная программа создает шаблоны меню динамически.

Чтобы привязать меню к окну, используйте функцию SetMenu, или определите дескриптор меню в параметре hMenu функции CreateWindowEx при создании окна. Другим способом, которым Вы можете привязать меню к окну, является определение шаблона меню, когда Вы регистрируете класс окна; шаблон идентифицирует определяемое меню как меню класса для этого класса окна.

Чтобы Windows автоматически связал конкретное меню с окном, определите шаблон меню, когда Вы регистрируете класс окна. Шаблон идентифицирует определяемое меню как меню класса для этого класса окна. Тогда, когда Вы создаете окно данного класса, Windows автоматически связывает заданное меню с окном.

Чтобы создать меню класса, включите идентификатор ресурса шаблона меню как член lpszMenuName структуры WNDCLASS, а затем передайте адрес структуры в функцию RegisterClass.


 

Создание меню класса

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

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

// Идентификатор ресурса шаблона меню
#define IDM_MYMENURESOURCE 3

Ниже следует важная часть самого приложения:

HINSTANCE hinst;

int APIENTRY WinMain(hinstance, hPrevInstance, lpCmdLine, nCmdShow)
HINSTANCE hinstance;
HINSTANCE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
{
MSG msg; // сообщение
WNDCLASS wc; // данные класса окна
HWND hwnd; // дескриптор главного окна

// Создадим класс окна для основного окна.
// Определим идентификатор ресурса шаблона меню как
// элемент lpszMenuName структуры WNDCLASS, чтобы
// создать меню класса.


wc.style = 0;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = MAKEINTRESOURCE(IDM_MYMENURESOURCE);
wc.lpszClassName = "MainWClass";

if (!RegisterClass(&wc))
return FALSE;

hinst = hinstance;

// Создадим основное окно. Установим параметр
// hmenu в значение ПУСТО (NULL) так, чтобы Windows
// использовал меню класса для окна.


hwnd = CreateWindow("MainWClass", "Sample Application",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance,
NULL);

if (hwnd == NULL)
return FALSE;

// Сделаем окно видимым и передадим сообщение
// WM_PAINT в оконную процедуру.


ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

// Запустим главный цикл сообщений.

while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(hPrevInstance);
}

LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
switch (uMsg) {
.
. // Обработка других сообщений окна.
.


case WM_COMMAND:
// Проверка идентификатора командного пункта меню.
switch(LOWORD(wParam)) {
case IDM_FI_OPEN:
DoFileOpen(); // определяется программой
break;
case IDM_FI_CLOSE:
DoFileClose(); // определяется программой
break;
.
. // Обработка других команд меню.
.

default:
break;
}
return 0;
.
. // Обработка других сообщений окна.
.


default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return NULL;
}

 

Создание вспомогательного (контекстного) меню

Чтобы использовать контекстное меню в прикладной программе, передайте ее дескриптор в функцию TrackPopupMenuEx. Приложение обычно вызывает TrackPopupMenuEx в оконной процедуре в ответ на сгенерированное пользователем сообщение, типа WM_LBUTTONDOWN или WM_KEYDOWN.

В дополнение к дескриптору выскакивающего меню, TrackPopupMenuEx требует, чтобы вы определили дескриптор окна владельца, позицию контекстного меню (в экранной системе координат) и кнопку мыши, которую пользователь может использовать, чтобы выбрать пункт.

Устаревшая функция TrackPopupMenu все еще поддерживается, но новые прикладные программы должны использовать функцию TrackPopupMenuEx. Функция TrackPopupMenuEx требует тех же самых параметров, что и TrackPopupMenu и, к тому же, позволяет вам определить часть экрана, которое меню не должно закрывать. Прикладная программа обычно вызывает эти функции в оконной процедуре при обработке сообщения WM_CONTEXTMENU.

Вы можете определить позицию контекстного меню, предоставляя x- и y-координаты вместе с флажком TPM_CENTERALIGN, TPM_LEFTALIGN или TPM_RIGHTALIGN. Флажок определяет позицию контекстного меню относительно x- и y-координат.

Вы должны дать возможность пользователю выбирать пункт из контекстного меню при помощи использования той же самая кнопки мыши, которая использовалась, чтобы показать на экране меню. Чтобы делать это, определите или флажок TPM_LEFTBUTTON или TPM_RIGHTBUTTON, чтобы указать, которую кнопку мыши пользователь может использовать, чтобы выбрать пункт меню.


 

Обработка сообщения WM_CONTEXTMENU

Сообщение WM_CONTEXTMENU создается тогда, когда оконная процедура прикладной программы посылает функции DefWindowProc сообщение WM_RBUTTONUP или WM_NCRBUTTONUP. Приложение может обработать это сообщение, чтобы показать на экране контекстное меню, в соответствующей конкретной части экрана. Если прикладная программа не показывает на экране контекстное меню, она должна передать это сообщение в DefWindowProc для обработки по умолчанию.

Ниже следует пример обработки сообщения WM_CONTEXTMENU, как оно может фигурировать в оконной процедуре прикладной программы. Младшие и старшие слова параметра lParam определяют экранную систему координат мыши, когда отпускается правая кнопка мыши. Определяемая программой функция OnContextMenu возвращает значение ИСТИНА (TRUE), если она показывает на экране контекстное меню или ЛОЖЬ (FALSE) если она этого не делает.

case WM_CONTEXTMENU:
if (!OnContextMenu(hwnd, LOWORD(lParam), HIWORD(lParam)))
return DefWindowProc(hwnd, uMsg, wParam, lParam);
break;

Следующая определяемая программой функция OnContextMenu показывает на экране контекстное меню, если определяемая позиция мыши внутри рабочей области окна. Более сложная функция могла бы показывать на экране одно из нескольких различных меню, в зависимости оттого, которая часть рабочей области определена. Чтобы фактически показывать на экране контекстное меню, этот пример вызывает определяемую программой функцию по имени DisplayContextMenu. За описанием этой функции, обратитесь к статье Отображение на экране контекстного меню.

BOOL WINAPI OnContextMenu(HWND hwnd, int x, int y)
{
RECT rc; // рабочая область окна
POINT pt = { x, y }; // место, где был щелчок мышью
// Получим ограничительный прямоугольник рабочей области.
GetClientRect(hwnd, &rc);
// Преобразуем позицию мыши в координаты рабочей области.
ScreenToClient(hwnd, &pt);
// Если позиция находится в рабочей области,
// показать контекстное меню.

if (PtInRect(&rc, pt)) {
ClientToScreen(hwnd, &pt);
DisplayContextMenu(hwnd, pt);
return TRUE;
}
// Возвратить значение ЛОЖЬ (FALSE), если
// не отображается никакого меню

return FALSE;
}

 

Создание контекстного меню Атрибуты Шрифта (Font-Attributes)

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

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

PopupMenu MENU
BEGIN
POPUP "Dummy Popup"
BEGIN
POPUP "Fonts"
BEGIN
MENUITEM "Courier", IDM_FONT_COURIER
MENUITEM "Times Roman", IDM_FONT_TMSRMN
MENUITEM "Swiss", IDM_FONT_SWISS
MENUITEM "Helvetica", IDM_FONT_HELV
MENUITEM "Old English", IDM_FONT_OLDENG
END
POPUP "Sizes"
BEGIN
MENUITEM "7", IDM_SIZE_7
MENUITEM "8", IDM_SIZE_8
MENUITEM "9", IDM_SIZE_9
MENUITEM "10", IDM_SIZE_10
MENUITEM "11", IDM_SIZE_11
MENUITEM "12", IDM_SIZE_12
MENUITEM "14", IDM_SIZE_14
END
POPUP "Styles"
BEGIN
MENUITEM "Bold", IDM_STYLE_BOLD
MENUITEM "Italic", IDM_STYLE_ITALIC
MENUITEM "Strike Out", IDM_STYLE_SO
MENUITEM "Superscript", IDM_STYLE_SUPER
MENUITEM "Subscript", IDM_STYLE_SUB
END
END
END

Следующий пример дает оконную процедуру и обеспечивающие функции, использованные, чтобы создавать и показывать на экране контекстное меню.

LRESULT APIENTRY MenuWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
RECT rc; // рабочая область
POINT pt; // место щелчка мыши
switch (uMsg) {
case WM_LBUTTONDOWN:
// Получим ограничительный прямоугольник рабочей области.
GetClientRect(hwnd, (LPRECT) &rc);
// Получим рабочие координаты щелчка мыши.
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
// Если щелчок клавишей мыши произошел
// внутри рабочей области, выполните определяемую
// программой функцию, которая показывает на
// экране контекстное меню.

if (PtInRect((LPRECT) &rc, pt))
HandlePopupMenu(hwnd, pt);
break;
.
. // Обработка других сообщений окна.
.

default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return NULL;
}

VOID APIENTRY HandlePopupMenu(hwnd, pt)
HWND hwnd;
POINT pt;
{
HMENU hmenu; // шаблон меню
HMENU hmenuTrackPopup; // контекстное меню
// Загрузите шаблон меню, содержащий контекстное меню из
// ресурсов прикладной программы.


hmenu = LoadMenu(hinst, "PopupMenu");
if (hmenu == NULL) return;

// Получим первое контекстное меню в шаблоне меню. Это -
// то меню, которое показывает TrackPopupMenu.


hmenuTrackPopup = GetSubMenu(hmenu, 0);

// TrackPopup использует экранную систему координат, чтобы преобразовать
// координаты щелчка клавишей мыши в экранную систему координат.


ClientToScreen(hwnd, (LPPOINT) &pt);

// Прорисовываем и устанавливаем контекстное меню.

TrackPopupMenu(hmenuTrackPopup, TPM_LEFTALIGN | TPM_LEFTBUTTON,
pt.x, pt.y, 0, hwnd, NULL);

// Разрушаем меню.

DestroyMenu(hmenu);
}

Отображение на экране контекстного меню

Функция, приведенная в следующем примере, показывает на экране контекстное меню.

Прикладная программа включает в себя ресурс меню, идентифицированный строкой "ShortcutExample". Строка меню просто содержит название меню. Прикладная программа использует функцию TrackPopupMenu, чтобы показать на экране меню, связанное с этим пунктом меню. (Сама строка меню не отображается, потому что TrackPopupMenu требует дескриптора меню, подменю или контекстного меню.)

VOID APIENTRY DisplayContextMenu(HWND hwnd, POINT pt)
{
HMENU hmenu; // меню верхнего уровня
HMENU hmenuTrackPopup; // контекстное меню>

// Загрузим ресурс меню.

if ((hmenu = LoadMenu(hinst, "ShortcutExample")) == NULL)
return;

// TrackPopupMenu не может показать на экране
// строку меню, так как получен дескриптор
// первого контекстного меню


hmenuTrackPopup = GetSubMenu(hmenu, 0);

// Покажем контекстное меню. Отследим правую кнопку мыши.

TrackPopupMenu(hmenuTrackPopup,
TPM_LEFTALIGN | TPM_RIGHTBUTTON,
pt.x, pt.y, 0, hwnd, NULL);

// Разрушим меню.
DestroyMenu(hmenu);
}


 

Использование в пунктах меню точечных рисунков (значков)

Windows, чтобы показать на экране пункт меню, может использовать вместо текстовой строки точечный рисунок( значок). Чтобы использовать значок, вы должны установить флажок MFT_BITMAP для пункта меню и определять дескриптор точечного рисунка, который Windows должен отобразить для пункта меню. Этот раздел описывает, как установить флажок MFT_BITMAP и извлечь данные о дескрипторе значка.

Прикладные программы, написанные в более ранних версиях Windows, могут устанавливать флажок MF_BITMAP со старыми наборами функций.

Установка флажка типа значка

Флажок MFT_BITMAP или MF_BITMAP сообщают, чтобы Windows преимущественно использовал точечный рисунок (значок), а не текстовую строку, чтобы показать на экране пункт меню. Флажок пункта меню MFT_BITMAP или MF_BITMAP должен быть установлен во время выполнения программы; Вы не можете установить его в файле определения ресурса.

Для новых прикладных программ, Вы можете использовать функцию SetMenuItemInfo или InsertMenuItem, чтобы установить флажок типа MFT_BITMAP. Чтобы заменить текстовый пункт меню на значок пункта меню, используйте SetMenuItemInfo. Чтобы добавить новый значок пункта к меню, используйте функцию InsertMenuItem.

Прикладные программы, написанные для более ранних версиий Windows, чтобы установить флажок MF_BITMAP, могут продолжать использовать функции ModifyMenu, InsertMenu или AppendMenu. Чтобы заменить текстовую строку пункта меню на значок пункта меню, используйте ModifyMenu. Чтобы добавить новый значок пункта меню, используйте флажок MF_BITMAP с функцией InsertMenu или AppendMenu.

Создание точечного рисунка (значка)

Когда Вы устанавливаете флажок типа MFT_BITMAP или MF_BITMAP для пункта меню, Вы должны также определить и дескриптор точечного рисунка (значка), который Windows должен отобразить для пункта меню. Вы можете представить значок как растровый ресурс или создавать точечный рисунок во время выполнения программы. Если Вы используете растровый ресурс, Вы можете использовать функцию LoadBitmap, чтобы загрузить значок и получить его дескриптор.

Чтобы создать значок во время выполнения программы, используйте функции графического интерфейса устройства (GDI). GDI предоставляет несколько способов, чтобы создать значок во время выполнения программы, но разработчики обычно используют следующий метод:

  1. Использовать функцию CreateCompatibleDC, чтобы создать контекст устройства, совместимый с контекстом устройства, использованным основным окном прикладной программы.
  2. Использовать функцию CreateCompatibleBitmap, чтобы создать значок, совместимый с основным окном прикладной программы или использовать функцию CreateBitmap, чтобы создать одноцветный значок.
  3. Использовать функцию SelectObject, чтобы выбрать значок в совместимом контексте устройства.
  4. Использовать рисующие функции GDI, типа Ellipse (Эллипс) и LineTo (Линия), чтобы нарисовать изображение в значок.

Для получения дополнительной информации, см. статью Точечные рисунки.


Добавление меню Линии и Диаграммы

Нижеследующий пример кода показывает, как создать меню, которое содержит значки пунктов меню. Он создает два меню. Первое - меню Chart (Диаграммы), которое содержит три значка пунктов меню: круговая диаграмма, линейная диаграмма и гистограмма. Пример показывает, как загрузить эти значки из файла ресурса прикладной программы, а затем использовать функции CreatePopupMenu и AppendMenu, чтобы создать меню и пункты меню.

Второе меню - меню Lines (Линии). Оно содержит значки, показывая типы линий, предоставляемые предопределенным пером в Windows. Значки типа линий созданы во время выполнения программы при помощи использования функций GDI.

Здесь определения растровых ресурсов в файле определения ресурса прикладной программы.

PIE BITMAP pie.bmp
LINE BITMAP line.bmp
BAR BITMAP bar.bmp

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

// Идентификаторы пункта меню

#define IDM_SOLID PS_SOLID
#define IDM_DASH PS_DASH
#define IDM_DASHDOT PS_DASHDOT
#define IDM_DASHDOTDOT PS_DASHDOTDOT

#define IDM_PIE 1
#define IDM_LINE 2
#define IDM_BAR 3

// Флажки типов линий

#define SOLID 0
#define DOT 1
#define DASH 2
#define DASHDOT 3
#define DASHDOTDOT 4

// Счет перьев

#define CPENS 5

// Флажки типа диаграммы

#define PIE 1

#define LINE 2
#define BAR 3

// Прототипы функций

LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM);
VOID MakeChartMenu(HWND);
VOID MakeLineMenu(HWND, HPEN, HBITMAP);

Следующий пример показывает, как меню и значки пункта меню создаются в приложении.

LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
static HPEN hpen[CPENS];
static HBITMAP hbmp[CPENS];
int i;

switch (uMsg) {
case WM_CREATE:

// Создадим меню Chart и Line
MakeChartMenu(hwnd);
MakeLineMenu(hwnd, hpen, hbmp);
return 0;

.
. // Обработаем другие сообщения окна.
.


case WM_DESTROY:

for (i = 0; i < CPENS; i++) {
DeleteObject(hbmp[i]);
DeleteObject(hpen[i]);
}

PostQuitMessage(0);
break;

default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return NULL;
}

VOID MakeChartMenu(hwnd)
HWND hwnd; // дескриптор окна владельца
{
HBITMAP hbmpPie; // дескриптор значка круговой диаграммы
HBITMAP hbmpLine; // дескриптор значка линейной диаграммы
HBITMAP hbmpBar; // дескриптор значка гистограммы
HMENU hmenuMain; // дескриптор главного меню
HMENU hmenuChart; // дескриптор меню Chart (диаграммы)

// Загрузим значки круговой, линейной диаграммы и
// гистограммы из файла определения ресурса.


hbmpPie = LoadBitmap(hinst, MAKEINTRESOURCE(PIE));
hbmpLine = LoadBitmap(hinst, MAKEINTRESOURCE(LINE));
hbmpBar = LoadBitmap(hinst, MAKEINTRESOURCE(BAR));

// Создадим меню Chart и добавим его к строке меню.
// Добавим в конец к меню Диаграммы пункты меню Круговая,
// Линейная и Гистограмма.


hmenuMain = GetMenu(hwnd);
hmenuChart = CreatePopupMenu();
AppendMenu(hmenuMain, MF_STRING | MF_POPUP, (UINT) hmenuChart,"Chart");
AppendMenu(hmenuChart, MF_BITMAP, IDM_PIE, (LPCTSTR) hbmpPie);
AppendMenu(hmenuChart, MF_BITMAP, IDM_LINE, (LPCTSTR) hbmpLine);
AppendMenu(hmenuChart, MF_BITMAP, IDM_BAR, (LPCTSTR) hbmpBar);

return;
}

VOID MakeLineMenu(hwnd, phpen, phbmp)
HWND hwnd;
HPEN *phpen;
HBITMAP *phbmp;
{
HMENU hmenuLines; // дескриптор меню Линии
HMENU hmenu; // дескриптор главного меню
COLORREF crMenuClr; // цвет фона пункта меню
HBRUSH hbrBackground; // дескриптор кисти фона
HBRUSH hbrOld; // дескриптор предыдущей кисти
LONG lCheckXY; // размеры значка-метки типа «галочки»
WORD wLineX; // ширина значка линии
WORD wLineY; // высота значка линии
HDC hdcMain; // дескриптор контекста устройства (DC) окна
HDC hdcLines; // дескриптор совместимого DC
HBITMAP hbmpOld; // дескриптор предыдущего значка
int i; // цикл счетчика

// Создадим меню Lines. Добавьте его к строке меню

hmenu = GetMenu(hwnd);

hmenuLines = CreatePopupMenu();
AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT) hmenuLines, "&Lines");

// Создадим кисть для цвета фона пункта меню.

crMenuClr = GetSysColor(COLOR_MENU);
hbrBackground = CreateSolidBrush(crMenuClr);

// Создадим совместимый контекст устройства значков линий,
// а затем выберем фоновую кисть в нем.


hdcMain = GetDC(hwnd);
hdcLines = CreateCompatibleDC(hdcMain);

hbrOld = SelectObject(hdcLines, hbrBackground);

// Получим габариты значка "галочки ". Ширина
// значка линии будет в пять раз шире значка "галочки".


lCheckXY = GetMenuCheckMarkDimensions();
wLineX = LOWORD(lCheckXY) * (WORD) 5;
wLineY = HIWORD(lCheckXY);

// Создадим значки и выберите их, по одному, в совместимом контексте устройства.
// Инициализируем каждый значок, заполняя его цветом фона пункта меню.


for (i = 0; i < CPENS; i++) {
phbmp[i] = CreateCompatibleBitmap(hdcMain, wLineX, wLineY);
if (i == 0)
hbmpOld = SelectObject(hdcLines, phbmp[i]);
else
SelectObject(hdcLines, phbmp[i]);
ExtFloodFill(hdcLines, 0, 0, crMenuClr, FLOODFILLBORDER);
}

// Создаем перья.

phpen[0] = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
phpen[1] = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
phpen[2] = CreatePen(PS_DASH, 1, RGB(0, 0, 0));
phpen[3] = CreatePen(PS_DASHDOT, 1, RGB(0, 0, 0));
phpen[4] = CreatePen(PS_DASHDOTDOT, 1, RGB(0, 0, 0));

// Выберем перо и значок в совместимом контексте устройства,
// проведем линию в значке, а затем добавим в конец значок
// как пункт в меню Lines.


for (i = 0; i < CPENS; i++) {
SelectObject(hdcLines, phbmp[i]);
SelectObject(hdcLines, phpen[i]);
MoveToEx(hdcLines, 0, wLineY / 2, NULL);
LineTo(hdcLines, wLineX, wLineY / 2);

AppendMenu(hmenuLines, MF_BITMAP, i + 1, (LPCTSTR) phbmp[i]);
}

// Освободим контекст устройства основного окна, и уничтожим
// совместимый контекст устройства. А также, уничтожим кисть фона.


ReleaseDC(hwnd, hdcMain);
SelectObject(hdcLines, hbrOld);
DeleteObject(hbrBackground);
SelectObject(hdcLines, hbmpOld);
DeleteDC(hdcLines);

return;
}


Пример значков пунктов меню

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

Первое меню содержит пункты меню, которые показывают каждую из трех типов диаграмм: круговую, линейную и гистограмму. Значки для этих пунктов меню определены как ресурсы и загружаются при помощи использования функции LoadBitmap. С этим меню, в горизонтальном меню, связано имя меню "Chart" (Диаграммы).

Второе меню содержит пункты меню, которые показывают каждый из пяти типов линии, использованных с функцией CreatePen: PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT и PS_DASHDOTDOT. Прикладная программа создает значки для этих пунктов меню во время выполнения программы, используя, рисующие функции GDI. С этим меню, в горизонтальном меню, связано имя меню "Lines" (Линии).

В оконной процедуре прикладной программы заданы два статических массива дескрипторов значков. Один массив содержит дескрипторы трех значков, использованных для меню Chart, другой содержит дескрипторы из пяти значков, использованных для меню Lines. При обработке сообщения WM_CREATE, оконная процедура загружает значки диаграмм, создает значки линий, а затем добавляет соответствующие пункты меню. При обработке WM_DESTROY сообщения, оконная процедура удаляет все значки.

Ниже следуют необходимые части заголовочного файла приложения.

// Идентификаторы пунктов меню

#define IDM_PIE 1
#define IDM_LINE 2
#define IDM_BAR 3
#define IDM_SOLID 4
#define IDM_DASH 5
#define IDM_DASHDOT 6
#define IDM_DASHDOTDOT 7

// Число пунктов в меню Chart и Lines

#define C_LINES 5
#define C_CHARTS 3

// Идентификаторы ресурса значков

#define IDB_PIE 1
#define IDB_LINE 2
#define IDB_BAR 3

// Размеры значков линий

#define CX_LINEBMP 40
#define CY_LINEBMP 10

Ниже следуют необходимые части оконной процедуры. Оконная процедура выполняет большинство инициализаций путем вызова определяемой программой функции LoadChartBitmaps, CreateLineBitmaps и AddBitmapMenu, описанные ниже в этом разделе.

LRESULT CALLBACK MainWindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
static HBITMAP aHbmLines[C_LINES];
static HBITMAP aHbmChart[C_CHARTS];
int i;

switch (uMsg) {

case WM_CREATE:

// Вызовите определяемые программой функции, чтобы загрузить
// значки для меню Chart и создать их для меню Lines.


LoadChartBitmaps(aHbmChart);
CreateLineBitmaps(aHbmLines);

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


AddBitmapMenu(
hwnd, // строка меню окна владельца
"&Chart", //текст имени меню в строке меню
IDM_PIE, // ID первого пункта меню
aHbmChart, // массив дескрипторов значков
C_CHARTS // число пунктов меню
);
AddBitmapMenu(hwnd, "&Lines", IDM_SOLID,
aHbmLines, C_LINES);
break;

case WM_DESTROY:
for (i = 0; i < C_LINES; i++)
DeleteObject(aHbmLines[i]);
for (i = 0; i < C_CHARTS; i++)
DeleteObject(aHbmChart[i]);
PostQuitMessage(0);
break;

.
. // здесь обрабатываются дополнительные сообщения.
.


default:
return (DefWindowProc(hwnd, uMsg, wParam, lParam));
}
return 0;
}

Определяемая программой функция LoadChartBitmaps загружает ресурсы значков для меню диаграмм путем вызова функция LoadBitmap, как ниже указано.

VOID WINAPI LoadChartBitmaps(HBITMAP *paHbm)
{
paHbm[0] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_PIE));
paHbm[1] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_LINE));
paHbm[2] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_BAR));
}

Определяемая программой функция CreateLineBitmaps создает значки для меню Lines при помощи использования, рисующих функций GDI. Функция создает контекст устройства в памяти (DC) с теми же самыми свойствами, что и DC главного окна программы. Для каждого типа линии, функция создает значок, выбирает его в DC в памяти и рисует в нем.

VOID WINAPI CreateLineBitmaps(HBITMAP *paHbm)
{
HWND hwndDesktop = GetDesktopWindow();
HDC hdcDesktop = GetDC(hwndDesktop);
HDC hdcMem = CreateCompatibleDC(hdcDesktop);
COLORREF clrMenu = GetSysColor(COLOR_MENU);
HBRUSH hbrOld;
HPEN hpenOld;
HBITMAP hbmOld;
int fnDrawMode;
int i;

// Создайте кисть, чтобы использовать цвет фона
// меню, и выберите его в DC в памяти.

hbrOld = SelectObject(hdcMem, CreateSolidBrush(clrMenu));

// Создайте значки. Выберите каждый из них в DC
// в памяти, которые были созданы и используйте их.


for (i = 0; i < C_LINES; i++) {
// Создадим точечный рисунок (значок) и выберем его в DC.

paHbm[i] = CreateCompatibleBitmap(hdcDesktop,
CX_LINEBMP, CY_LINEBMP);
hbmOld = SelectObject(hdcMem, paHbm[i]);

// Заполним фон, используя кисть.

PatBlt(hdcMem, 0, 0, CX_LINEBMP, CY_LINEBMP, PATCOPY);

// Создадим перо и выберем его в DC.

hpenOld = SelectObject(hdcMem,
CreatePen(PS_SOLID + i, 1, RGB(0, 0, 0)));

// Пропишем строку. Сохраним цвет фона, где
// перо является белым, используем режим рисунка R2_MASKPEN.


fnDrawMode = SetROP2(hdcMem, R2_MASKPEN);
MoveToEx(hdcMem, 0, CY_LINEBMP / 2, NULL);
LineTo(hdcMem, CX_LINEBMP, CY_LINEBMP / 2);
SetROP2(hdcMem, fnDrawMode);

// Уничтожим перо и выберем старое перо и значок.

DeleteObject(SelectObject(hdcMem, hpenOld));
SelectObject(hdcMem, hbmOld);
}
// Уничтожим кисть и выберем первоначальную кисть.
DeleteObject(SelectObject(hdcMem, hbrOld));
// Очистим память, отведенную для DC и освободим DC рабочего стола.
DeleteDC(hdcMem);
ReleaseDC(hwndDesktop, hdcDesktop);
}

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

VOID WINAPI AddBitmapMenu(
HWND hwnd, // окно, которое владеет строкой меню
LPSTR lpszText, // текст имени меню на строке меню
UINT uID, // ID первого значка пункта меню
HBITMAP *paHbm, // значки для пунктов меню
int cItems) // число значков пунктов меню
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup = CreatePopupMenu();
MENUITEMINFO mii;
int i;

// Добавляем значки пунктов меню к меню.

for (i = 0; i < cItems; i++) {
mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
mii.fType = MFT_BITMAP;
mii.wID = uID + i;
mii.dwTypeData = (LPSTR) (paHbm[i]);
InsertMenuItem(hmenuPopup, i, TRUE, &mii);
}

// Добавляем имя меню в строку меню.

mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_SUBMENU;
mii.fType = MFT_STRING;
mii.hSubMenu = hmenuPopup;
mii.dwTypeData = lpszText;
InsertMenuItem(hmenuBar,
GetMenuItemCount(hmenuBar), TRUE, &mii);
}

Создание нарисованных пользователем пунктов меню

Если Вы нуждаетесь в полном контроле над внешним видом пункта меню, Вы можете использовать в вашей прикладной программе нарисованный пользователем пункт меню. Этот раздел описывает шаги, которые приведут к созданию и использованию нарисованного пользователем пункта меню.

Установка флажков при рисовании пользователем пунктов меню

Вы не можете задать нарисованный пользователем пункт меню в файле определения ресурса вашей прикладной программы. Вместо этого, Вы должны создать новый пункт меню или изменить существующий при помощи использования флажка меню MFT_OWNERDRAW.

Вы можете использовать функцию InsertMenuItem или SetMenuItemInfo, чтобы определить нарисованный пользователем пункт меню. Используйте InsertMenuItem, чтобы вставить новый пункт меню в заданной позиции в строке меню или меню. Используйте SetMenuItemInfo, чтобы изменить состав меню.

При вызове этих двух функций, Вы должны определить адрес структуры MENUITEMINFO, которая описывает свойства нового пункта меню или свойства, которые Вы желаете изменить для существующего пункта меню. Чтобы сделать пункт меню, пунктом нарисованным пользователем, определите значение MIIM_TYPE для члена fMask и значение MFT_OWNERDRAW для члена fType структуры.

Устанавливая соответствующие члены структуры MENUITEMINFO, Вы можете связывать определяемое программой значение, которое называется данными пункта (item data), с каждым пунктом меню. Поступая таким образом, определите значение MIIM_DATA для члена fMask и определяемого программой значения для члена dwItemData структуры.

Вы можете использовать данные пункта с любым типом пункта меню, но это особенно полезно для нарисованных пользователем пунктов. Например, предположим, что структура содержит информацию, используемую, чтобы нарисовать пункт меню. Прикладная программа могла бы использовать эти данные пункта для пункта меню, чтобы сохранить указатель на структуру. Данные пункта пересылаются в окно владельца меню с сообщениями WM_DRAWITEM и WM_MEASUREITEM. Чтобы в любое время извлечь данные о пункте для меню, используйте функцию GetMenuItemInfo.

Прикладные программы, написанные в более ранних версиях Windows, могут продолжать вызывать функции AppendMenu, InsertMenu или ModifyMenu, чтобы назначить флажок MF_OWNERDRAW нарисованному пользователем пункту меню.

Когда Вы вызываете любую из этих трех функций, Вы можете передавать 32-разрядное значение как параметр lpNewItem. Это значение может представлять любую информацию, которая является значимой для вашей прикладной программы, и она будет доступна вашему приложению, когда пункт должен отобразиться. Например, значение могло бы содержать указатель на структуру; структура, в свою очередь, могла бы содержать текстовую строку и дескриптор логического шрифта, который ваша прикладная программа будет использовать, чтобы рисовать строку.

Нарисованные пользователем меню и сообщение WM_MEASUREITEM

Прежде, чем Windows покажет на экране нарисованный пользователем пункт меню впервые, она передает сообщение WM_MEASUREITEM оконной процедуре окна, которое владеет пунктами меню. Это сообщение содержит указатель на структуру MEASUREITEMSTRUCT, которая идентифицирует пункт и содержит данные пункта, которые прикладная программа, возможно, присвоила ему. Оконная процедура должна заполнить члены itemWidth и itemHeight структуры перед возвращением из обработки сообщения. Windows использует информацию в этих элементах при создании ограничительного прямоугольника, в котором прикладная программа рисует пункт меню. К тому же она использует эту информацию, чтобы определить, когда пользователь выбирает пункт.

Нарисованные пользователем меню и сообщение WM_DRAWITEM

Всякий раз, когда пункт должен быть прорисован (например, когда он первоначально отображается или когда пользователь выбирается его), Windows отправляет сообщение WM_DRAWITEM оконной процедуре окна владельца меню. Это сообщение содержит указатель на структуру DRAWITEMSTRUCT содержащую информацию о пункте, включая данные о пункте, которые прикладная программа, возможно, присвоила ему. Кроме того, DRAWITEMSTRUCT содержит флажки, которые обозначают состояние пункта (типа того, является ли он недоступным или выбираемым с отметкой галочкой), а также ограничительный прямоугольник и контекст устройства, которые прикладная программа использует, чтобы рисовать пункт.

Прикладная программа должна делать следующее при обработке сообщения WM_DRAWITEM:

  1. Установить тип рисунка, который является необходимым. Чтобы сделать это, проверьте член itemAction структуры DRAWITEMSTRUCT.
  2. Нарисовать соответствующий пункт меню, используя ограничительный прямоугольник и контекст устройства, полученный из структуры DRAWITEMSTRUCT. Прикладная программа должна рисовать только внутри ограничительного прямоугольника. По причинам производительности, Windows не обрезает части изображения, которые прорисованы снаружи прямоугольника.
  3. Восстановить все объекты GDI, выбранные для пунктов меню в контексте устройства.

Если пользователь выбирает пункт меню, Windows устанавливает член itemAction структуры DRAWITEMSTRUCT в значение ODA_SELECT и устанавливает значение ODS_SELECTED в элементе itemState. Это - команда вызова подпрограммы приложения, которая перерисует пункт меню, чтобы указать, что он выбран.



Настройка шрифтов для текстовых строк пункта меню

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

Здесь меню, заданное в файле определения ресурса. Обратите внимание, что строки для пунктов меню Regular (Обычный), Bold (Полужирный), Italic (Курсив) и Underline (Подчеркнутый) назначены во время выполнения программы, так что их строки пусты в файле определения ресурса.

MainMenu MENU
BEGIN
POPUP "&Character"
BEGIN
MENUITEM "", IDM_REGULAR
MENUITEM SEPARATOR
MENUITEM "", IDM_BOLD
MENUITEM "", IDM_ITALIC
MENUITEM "", IDM_ULINE
END
END

Оконная процедура прикладной программы обрабатывает сообщения, включающие использование нарисованных пользователем пунктов меню. Приложение использует сообщение WM_CREATE, чтобы сделать следующее:

Установить флажок MF_OWNERDRAW для пунктов меню.

Установить строки текста для пунктов меню.

Получить дескрипторы шрифтов, использованных для прорисовки этих пунктов.

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

Дескрипторы текстовых строк и шрифта сохраняются в массиве определяемых программой структур MYITEM. Определяемая программой функция GetAFont создает шрифт, который соответствует атрибутам данного шрифта и возвращает значение дескриптора шрифта. Дескрипторы разрушаются в ходе обработки сообщения WM_DESTROY.

В ходе обработки сообщения WM_MEASUREITEM, пример получает ширину и высоту строки пункта меню и копирует эти значения в структуру MEASUREITEMSTRUCT. Windows использует значения ширины и высоты, чтобы вычислить размер меню.

В ходе обработки сообщения WM_DRAWITEM, строка пункта меню рисуется, оставляя место рядом со строкой для значка "галочки ". Если пользователь выбирает пункт, цвет выбранного текста и фона используются, чтобы прорисовать пункт.

LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{

typedef struct _MYITEM {
HFONT hfont;
LPSTR psz;
} MYITEM; // структура для шрифта и строки пункта

MYITEM *pmyitem; // указатель на шрифт и строку пункта
static MYITEM myitem[CITEMS]; // массив MYITEMS
static HMENU hmenu; // дескриптор главного меню
static COLORREF crSelText; // цвет текста выбранного пункта
static COLORREF crSelBkgnd; // цвет фона выбранного пункта
COLORREF crText; // цвет текста невыбранного пункта
COLORREF crBkgnd; // цвет фона невыбранного пункта
LPMEASUREITEMSTRUCT lpmis; // указывает на данные пункта
LPDRAWITEMSTRUCT lpdis; // указывает на данные для прорисовки пункта
HDC hdc; // дескриптор экранного DC
SIZE size; // протяженность текста пункта меню
DWORD dwCheckXY; // размеры метки «галочка»
WORD wCheckX; // ширина метки «галочка»
int nTextX; // ширина пункта меню
int nTextY; // высота пункта меню
int i; // цикл счета
HFONT hfontOld; // дескриптор старого шрифта
BOOL fSelected = FALSE; // флажок выбора пункта меню

switch (uMsg) {
case WM_CREATE:

// Изменим пункты меню Regular (Обычный), Bold (Полужирный), Italic
// (Курсив) и Underline (Подчеркнутый), чтобы делать их нарисованными
// пользователем пунктами. Свяжем структуру MYITEM с каждым пунктом,
// которые содержат строку и шрифт, чтобы обработать каждый пункт.


hmenu = GetMenu(hwnd);
ModifyMenu(hmenu, IDM_REGULAR, MF_BYCOMMAND |
MF_CHECKED | MF_OWNERDRAW, IDM_REGULAR,
(LPTSTR) &myitem[REGULAR]);
ModifyMenu(hmenu, IDM_BOLD, MF_BYCOMMAND |
MF_OWNERDRAW, IDM_BOLD, (LPTSTR) &myitem[BOLD]);
ModifyMenu(hmenu, IDM_ITALIC, MF_BYCOMMAND |
MF_OWNERDRAW, IDM_ITALIC,
(LPTSTR) &myitem[ITALIC]);
ModifyMenu(hmenu, IDM_ULINE, MF_BYCOMMAND |
MF_OWNERDRAW, IDM_ULINE,(LPTSTR)&myitem[ULINE]);

// Извлечем дескриптор шрифта каждого пункта и скопируем их в
// элемент hfont структуры MYITEM каждого пункта.
// А также, скопируем в структуры строку каждого пункта

myitem[REGULAR].hfont = GetAFont(REGULAR);
myitem[REGULAR].psz = "Regular";
myitem[BOLD].hfont = GetAFont(BOLD);
myitem[BOLD].psz = "Bold";
myitem[ITALIC].hfont = GetAFont(ITALIC);
myitem[ITALIC].psz = "Italic";
myitem[ULINE].hfont = GetAFont(ULINE);
myitem[ULINE].psz = "Underline";

// Извлечем текст и цвет фона выбранного текста меню.

crSelText = GetSysColor(COLOR_HIGHLIGHTTEXT);
crSelBkgnd = GetSysColor(COLOR_HIGHLIGHT);

return 0;

case WM_MEASUREITEM:

// Извлечем контекст устройства главного окна.

hdc = GetDC(hwnd);


// Извлечем указатели на пункты меню в
// структурах MEASUREITEMSTRUCT и MYITEM.


lpmis = (LPMEASUREITEMSTRUCT) lParam;
pmyitem = (MYITEM *) lpmis->itemData;

// Выберем шрифт, связанный пунктом в
// контексте устройства главного окна.


hfontOld = SelectObject(hdc, pmyitem->hfont);

// Извлечем ширину и высоту строки пункта,
// а затем скопируем их в элементы itemWidth
// и itemHeight структуры MEASUREITEMSTRUCT.


GetTextExtentPoint32(hdc, pmyitem->psz,
lstrlen(pmyitem->psz), &size);
lpmis->itemWidth = size.cx;
lpmis->itemHeight = size.cy;

// В контексте устройства обратно выберем старый
// шрифт, и затем освободим контекст устройства.


SelectObject(hdc, hfontOld);
ReleaseDC(hwnd, hdc);

return TRUE;

break;

case WM_DRAWITEM:

// Получим указатели на пункты меню в структурах
// DRAWITEMSTRUCT и MYITEM.


lpdis = (LPDRAWITEMSTRUCT) lParam;
pmyitem = (MYITEM *) lpdis->itemData;

// Если пользователь выбрал пункт, используйте выбранный
// текст и цвет фона для отображения пункта.


if (lpdis->itemState & ODS_SELECTED) {
crText = SetTextColor(lpdis->hDC, crSelText);
crBkgnd = SetBkColor(lpdis->hDC, crSelBkgnd);
fSelected = TRUE;
}

// Не забудьте оставить пространство в пункте
// меню для значка "галочки ". Извлеките ширину значка
// и добавьте ее к ширине пункта меню.


dwCheckXY = GetMenuCheckMarkDimensions();

wCheckX = LOWORD(dwCheckXY);
nTextX = wCheckX + lpdis->rcItem.left;
nTextY = lpdis->rcItem.top;

// Выберите шрифт, связанный с пунктом в контексте
// устройства пункта, а затем нарисуйте строку.


hfontOld = SelectObject(lpdis->hDC, pmyitem->hfont);
ExtTextOut(lpdis->hDC, nTextX, nTextY, ETO_OPAQUE,
&lpdis->rcItem, pmyitem->psz,
lstrlen(pmyitem->psz), NULL);


// Выберите обратно предыдущий шрифт в контексте устройства.

SelectObject(lpdis->hDC, hfontOld);

// Возвратите текст и цвета фона в
// их нормальное состояние (не выбранное).


if (fSelected) {
SetTextColor(lpdis->hDC, crText);
SetBkColor(lpdis->hDC, crBkgnd);
}

return TRUE;

.
. // Обработка других сообщений.
.


case WM_DESTROY:

// Уничтожим дескрипторы шрифта пунктов меню.

for (i = 0; i < CITEMS; i++)
DeleteObject(myitem[i].hfont);

PostQuitMessage(0);
break;

default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return NULL;
}

HFONT GetAFont(fnFont)
int fnFont; // флажки атрибутов шрифта
{
static LOGFONT lf; // структура для информации о шрифте


// Получим дескриптор для моноширинного шрифта ANSI, а информацию о шрифте скопируем
// в структуру LOGFONT.


GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(LOGFONT), &lf);

// Установим соответствующие атрибуты шрифта.

if (fnFont == BOLD)
lf.lfWeight = FW_BOLD;
else
lf.lfWeight = FW_NORMAL;

lf.lfItalic = (fnFont == ITALIC);
lf.lfItalic = (fnFont == ULINE);

// создадим шрифт, а затем возвратим его дескриптор.

return CreateFont(lf.lfHeight, lf.lfWidth,
lf.lfEscapement, lf.lfOrientation, lf.lfWeight,
lf.lfItalic, lf.lfUnderline, lf.lfStrikeOut,
lf.lfCharSet, lf.lfOutPrecision, lf.lfClipPrecision,
lf.lfQuality, lf.lfPitchAndFamily, lf.lfFaceName);
}

Пример "собственных" пунктов меню

Пример в этой теме использует в меню "собственные" пункты. Пункты меню выбирают конкретные атрибуты шрифта, а прикладная программа показывает на экране каждый пункт меню, используя шрифт, который имеет соответствующий атрибут. Например, пункт меню Italic (Курсив) отображается в курсивном шрифте. Название меню Character (Шрифт) в строке меню открывает меню.

Строка меню и раскрывающееся меню определены вначале расширенным ресурсом шаблона меню. Поскольку шаблон меню не может определять "собственные" пункты, меню вначале содержит четыре текстовых пункта меню со следующими строками: "Regular" ("Обычный"), "Bold" ("Полужирный"), "Italic" ("Курсив") и "UnderlineWM_CREATE. Когда процедура получает сообщение WM_CREATE, она вызывает определяемую программой функцию OnCreate, которая выполняет следующие шаги для каждого пункта меню:

  1. Распределяет в памяти определяемую программой структуру MYITEM.
  2. Получает текст пункта меню и сохраняет его в определяемой программой структуре MYITEM.
  3. Создает шрифт, использованный, чтобы показать на экране пункт меню и сохраняет дескриптор в определяемой программой структуре MYITEM.
  4. Изменяет тип пункта меню на MFT_OWNERDRAW и сохраняет указатель на определяемую программой структуру MYITEM, как на данные пункта.

Поскольку указатель на каждую определяемую программой структуру MYITEM сохраняется как данные пункта, он переходит в оконную процедуру вместе с сообщениями WM_MEASUREITEM и WM_DRAWITEM для соответствующего пункта меню. Указатель содержится в члене itemData и структуры MEASUREITEMSTRUCT и структуры DRAWITEMSTRUCT.

Сообщение WM_MEASUREITEM отправляется для каждого "собственного" пункта меню, когда он впервые отображается на экране. Приложение обрабатывает это сообщение, выбирая шрифт для пункта меню в контекст устройства, а затем устанавливает пространство, требуемое, чтобы показать на экране текст пункта меню этим шрифтом. Шрифт и текст пункта меню оба определены структурой пункта меню MYITEM (структура, определенная прикладной программой). Приложение устанавливает размер текста при помощи использования функции GetTextExtentPoint32.

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

Оконная процедура обрабатывает сообщение WM_DESTROY, чтобы уничтожить шрифты и освободить память. Прикладная программа удаляет шрифт и освобождает определяемую программой структуру MYITEM для каждого пункта меню.

Ниже следуют необходимые части заголовочного файла прикладной программы.

// Идентификаторы пункта меню для меню Character (Шрифт)

#define IDM_CHARACTER 10
#define IDM_REGULAR 11
#define IDM_BOLD 12
#define IDM_ITALIC 13
#define IDM_UNDERLINE 14

// Структура, связанная с пунктами меню

typedef struct tagMYITEM {
HFONT hfont;
int cchItemText;
char szItemText[1];
} MYITEM;

#define CCH_MAXITEMTEXT 256

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

LRESULT CALLBACK MainWindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg) {
case WM_CREATE:
if (!OnCreate(hwnd))
return -1;
break;

case WM_DESTROY:
OnDestroy(hwnd);
PostQuitMessage(0);
break;

case WM_MEASUREITEM:
OnMeasureItem(hwnd, (LPMEASUREITEMSTRUCT) lParam);

return TRUE;

case WM_DRAWITEM:
OnDrawItem(hwnd, (LPDRAWITEMSTRUCT) lParam);
return TRUE;

.
. // Дополнительная обработка сообщения идет здесь.
.


default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}

BOOL WINAPI OnCreate(HWND hwnd)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;
UINT uID;

MYITEM *pMyItem;

// Получим дескриптор выскакивающего меню.

mii.fMask = MIIM_SUBMENU; <>// чтобы получить информацию
GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Модифицируем каждый пункт меню. Предположим, что
// IDs ( идентификаторы) от IDM_REGULAR до
// IDM_UNDERLINE - последовательные числа


for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) {

// Распределим в памяти структуру пункта,
// оставляя пространство для строки плюс для
// символов CCH_MAXITEMTEXT.


pMyItem = (MYITEM *) LocalAlloc(LMEM_FIXED,
sizeof(MYITEM) + CCH_MAXITEMTEXT);

// Сохраним текст пункта в структуре пункта

mii.fMask = MIIM_TYPE;
mii.dwTypeData = pMyItem->szItemText;
mii.cch = CCH_MAXITEMTEXT;
GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
pMyItem->cchItemText = mii.cch;

// Перераспределим память для структуры для
// минимально требуемого размера.


pMyItem = (MYITEM *) LocalReAlloc(pMyItem,
sizeof(MYITEM) + mii.cch, LMEM_MOVEABLE);

// Создадим шрифт, который используем для прорисовки пункта.

pMyItem->hfont = CreateMenuItemFont(uID);

// Заменим пункт на "собственный" пункт и сохраним
// адрес структуры пункта как данные пункта.


mii.fMask = MIIM_TYPE | MIIM_DATA;
mii.fType = MFT_OWNERDRAW;
mii.dwItemData = (DWORD) pMyItem;

SetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
}
return TRUE;
}

HFONT CreateMenuItemFont(UINT uID)
{
LOGFONT lf;

ZeroMemory(&lf, sizeof(lf));
lf.lfHeight = 20;
lstrcpy(lf.lfFaceName, "Times New Roman");

switch (uID) {
case IDM_BOLD:
lf.lfWeight = FW_HEAVY;
break;

case IDM_ITALIC:
lf.lfItalic = TRUE;
break;

case IDM_UNDERLINE:

lf.lfUnderline = TRUE;
break;
}
return CreateFontIndirect(&lf);
}

VOID WINAPI OnDestroy(HWND hwnd)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;
UINT uID;
MYITEM *pMyItem;

// Получим дескриптор меню.

mii.fMask = MIIM_SUBMENU; // чтобы получить информацию
GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Освободим ресурсы, связанные с каждым пунктом меню.

for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) {

// Получим данные пункта.

mii.fMask = MIIM_DATA;
GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
pMyItem = (MYITEM *) mii.dwItemData;

// Уничтожим шрифт и освободим структуру пункта

DeleteObject(pMyItem->hfont);
LocalFree(pMyItem);
}
}

VOID WINAPI OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpmis)
{
MYITEM *pMyItem = (MYITEM *) lpmis->itemData;
HDC hdc = GetDC(hwnd);
HFONT hfntOld = SelectObject(hdc, pMyItem->hfont);
SIZE size;
GetTextExtentPoint32(hdc, pMyItem->szItemText, pMyItem->cchItemText, &size);
lpmis->itemWidth = size.cx;
lpmis->itemHeight = size.cy;
SelectObject(hdc, hfntOld);
ReleaseDC(hwnd, hdc);
}

VOID WINAPI OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis)
{
MYITEM *pMyItem = (MYITEM *) lpdis->itemData;
COLORREF clrPrevText, clrPrevBkgnd;
HFONT hfntPrev;
int x, y;
// Установим соответствующие цвета шрифта и фона.
if (lpdis->itemState & ODS_SELECTED) {
clrPrevText = SetTextColor(lpdis->hDC,
GetSysColor(COLOR_HIGHLIGHTTEXT));
clrPrevBkgnd = SetBkColor(lpdis->hDC,
GetSysColor(COLOR_HIGHLIGHT));
}
else {
clrPrevText = SetTextColor(lpdis->hDC,
GetSysColor(COLOR_MENUTEXT));
clrPrevBkgnd = SetBkColor(lpdis->hDC,

GetSysColor(COLOR_MENU));
}

// Решим, где нарисовать и оставить пространство для "галочки".
x = lpdis->rcItem.left;
y = lpdis->rcItem.top;
x += LOWORD(GetMenuCheckMarkDimensions());

// Выберем шрифт и прорисуем текст.

hfntPrev = SelectObject(lpdis->hDC, pMyItem->hfont);
ExtTextOut(lpdis->hDC, x, y, ETO_OPAQUE,
&lpdis->rcItem, pMyItem->szItemText,
pMyItem->cchItemText, NULL);


// Восстанавливаем первоначальный шрифт и цвет.

SelectObject(lpdis->hDC, hfntPrev);
SetTextColor(lpdis->hDC, clrPrevText);
SetBkColor(lpdis->hDC, clrPrevBkgnd);
}

Использование индивидуальных значков типа "галочки"

Windows предоставляет заданный по умолчанию значок "галочки " для отображения на экране рядом с пунктом меню, который отмечается ею. Вы можете настроить индивидуальный пункт меню, при помощи предоставления пары точечных рисунков (значков), чтобы заменить заданный по умолчанию значок "галочки ". Windows показывает на экране один значок, когда пункт отмечен "галочкой", а другой, когда у него отметка "галочкой" снята. Этот раздел описывает шаги, приводящие к созданию и использованию пользовательских точечных рисунков (значков) типа "галочки ".

Создание пользовательских точечных рисунков (значков) типа "галочки"

Пользовательский точечный рисунок (значок) "галочки" должен быть такого же самого размера как и заданный по умолчанию значок "галочки". Вы можете получить заданный по умолчанию размер значка "галочки" путем вызова функции GetMenuCheckMarkDimensions. Младшее слово величины возвращаемого значения этой функции определяет ширину; старшее слово - высоту.

Вы можете использовать растровые ресурсы, чтобы обеспечить программу значками типа "галочки". Однако, поскольку требуемый размер точечного рисунка изменяется в зависимости от типа дисплея, вам видимо придется по необходимости изменить размеры значка во время выполнения программы при помощи использования функции StretchBlt. Зависимость от значка, искажение, вызываемое изменением размеров, может привести к неприятному результату.

Вместо того, чтобы использовать растровый ресурс, Вы можете создать точечный рисунок (значок) во время выполнения программы при помощи использования функций GDI.

Чтобы создать значок во время выполнения программы:

  1. Используйте функцию CreateCompatibleDC, чтобы создать контекст устройства, совместимый с тем, который используется основным окном прикладной программы. Параметр функции hdc может установить или значение ПУСТО (NULL) или величину возвращаемого значения из функции GetDC. CreateCompatibleDC возвращает значение дескриптора совместимого контекста устройства.
  2. Используйте функцию CreateCompatibleBitmap, чтобы создать точечный рисунок (значок), совместимый с основным окном приложения. Параметры nWidth и nHeight этой функции устанавливают размеры значка; они должны определить информацию о ширине и высоте, возвращаемую функцией GetMenuCheckMarkDimensions. Вы можете также использовать и функцию CreateBitmap, чтобы создать одноцветный значок.
  3. Используйте функцию SelectObject для выбора значка в совместимом контексте устройства.
  4. Используйте рисующие функции GDI, такие как Ellipse (Эллипс) и LineTo, (Линия), чтобы нарисовать изображение внутри значка, или функции типа BitBlt и StretchBlt, чтобы копировать изображение в точечный рисунок (значок).

За большей информацией обратитесь к статье Bitmaps.

Связывание пункта меню с точечным рисунком (значком)

Вы связываете пару точечных рисунков (значков) "галочки" с пунктом меню, передавая дескрипторы значков функции SetMenuItemBitmaps. Параметр hBitmapUnchecked идентифицирует значок снятия "галочки", а параметр hBitmapChecked идентифицирует установку значка. Если Вы хотите удалить один или оба значка «галочки» из пункта меню, Вы можете установить или параметр hBitmapUnchecked или параметр hBitmapChecked, или оба в значение ПУСТО (NULL).

Настройка атрибута "галочка"

Функция CheckMenuItem устанавливает атрибут "галочки" пункта меню, либо в установленное, либо в снятое состояние. Вы можете определить значение MF_CHECKED, чтобы установить атрибут "галочку " для отметки и значение MF_UNCHECKED, чтобы установить его в снятое состояние.

Вы можете также устанавливать состояние "галочки" пункта меню при помощи использования функция SetMenuItemInfo.

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



Моделирование окошек для флажка "галочкой" в меню

Эта тема содержит пример, который показывает, как моделировать окошки для флажка "галочкой" в меню. Пример содержит меню Character (Символ), пункты которого позволяют пользователю устанавливать атрибуты текущего шрифта, такие как полужирный, курсивный и подчеркнутый. Когда атрибут шрифта действует, галочка отображается в окошке для флажка рядом с соответствующим пунктом меню; иначе, рядом с этим пунктом отображается пустое окошко для флажка.

Пример заменяет заданный по умолчанию точечный рисунок "галочки " на два точечных рисунка (значка): точечный рисунок с установленным в окне маркером и точечным рисунком с пустым окном (без маркера). Точечный рисунок окошка для маркера отображается рядом с пунктом меню Bold (Полужирный), Italic (Курсивный) или Underline (Подчеркнутый), когда атрибут "галочки " пункта установлен в значение MF_CHECKED. Снятый маркер "галочки" или пустой точечный рисунок окошка для маркера отображается тогда, когда атрибут "галочки " установлен в значение MF_UNCHECKED.

Windows предоставляет предопределенный точечный рисунок (значок), который содержит изображения, используемые для окошек с маркером "галочка" и радио-кнопок. Пример разделяет пустые окошки для "галочки" и с маркером, копирует их в два отдельных точечных рисунка (значки), а затем использует их как значки установки и снятия отметки "галочкой" для пунктов в меню Character. (Символ).

Чтобы получить дескриптор заданного системой точечного рисунка (значка) окошка для флажка, пример вызывает функцию LoadBitmap, устанавливая значение ПУСТО (NULL) в параметре hInstance и OBM_CHECKBOXES в параметре lpBitmapName. Поскольку все изображения значков одного и того же размера, пример может разделять их, поделив ширину и высоту точечного рисунка на число изображений в строках и столбцах.

Нижеследующая часть файла определения ресурса показывает, как определены пункты меню в меню Character. Обратите внимание!, что сначала атрибуты шрифта не действуют, так как атрибут "галочки " для пункта Обычный (Regular) установлен в отмеченное "галочкой" состояние, а, по умолчанию, атрибут "галочки " оставшихся пунктов установлен в состояние без "галочки".

#include "men3.h"

MainMenu MENU
BEGIN
POPUP "&Character"
BEGIN
MENUITEM "&Regular", IDM_REGULAR, CHECKED
MENUITEM SEPARATOR
MENUITEM "&Bold", IDM_BOLD
MENUITEM "&Italic", IDM_ITALIC
MENUITEM "&Underline", IDM_ULINE
END
END

Здесь находится относящееся к делу содержание заголовочного файла прикладной программы.

// Идентификаторы пунктов меню

#define IDM_REGULAR 0x1
#define IDM_BOLD 0x2
#define IDM_ITALIC 0x4
#define IDM_ULINE 0x8

// Флажки - маркеры

#define CHECK 1
#define UNCHECK 2

// Маска атрибута шрифта

#define ATTRIBMASK 0xe

// Прототипы функций

LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM);
HBITMAP GetMyCheckBitmaps(UINT);
BYTE CheckOrUncheckMenuItem(BYTE, HMENU);

Следующий пример показывает части оконной процедуры, которые создают точечные рисунки (значки) "галочки "; устанавливает атрибут "галочки" пунктов меню Bold (Полужирный), Italic (Курсивный) и Underline(Подчеркнутый); и уничтожает значки "галочки".

LRESULT APIENTRY MainWndProc(hwndMain, uMsg, wParam, lParam)
HWND hwndMain;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
static HBITMAP hbmpCheck; // дескриптор значка установленной «галочки»
static HBITMAP hbmpUncheck; // дескриптор значка без «галочки»
static HMENU hmenu; // дескриптор главного окна
BYTE fbFontAttrib; // флажки атрибутов шрифта

switch (uMsg) {
case WM_CREATE:

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

hbmpCheck = GetMyCheckBitmaps(CHECK);
hbmpUncheck = GetMyCheckBitmaps(UNCHECK);

// Установим значки для маркера и снятия маркера
// "галочки" для пунктов меню.


hmenu = GetMenu(hwndMain);
SetMenuItemBitmaps(hmenu, IDM_BOLD, MF_BYCOMMAND,
hbmpUncheck, hbmpCheck);
SetMenuItemBitmaps(hmenu, IDM_ITALIC, MF_BYCOMMAND,
hbmpUncheck, hbmpCheck);
SetMenuItemBitmaps(hmenu, IDM_ULINE, MF_BYCOMMAND,
hbmpUncheck, hbmpCheck);

return 0;

case WM_COMMAND:
switch (LOWORD(wParam)) {

// Обработка команд меню.

case IDM_REGULAR:
case IDM_BOLD:
case IDM_ITALIC:
case IDM_ULINE:

// CheckOrUncheckMenuItem – функция, определяемая
// прикладной программой, которая устанавливает "галочки"
// пунктам меню и возвращает выбранный пользователем
// атрибут шрифта.


fbFontAttrib = CheckOrUncheckMenuItem(
(BYTE) LOWORD(wParam), hmenu);

.
. // Установим атрибуты шрифта.
.


return 0;

.
. // Обработаем другие командные сообщения.
.


default:
break;
}

break;

.
. // Обработаем другие сообщения окна.
.



case WM_DESTROY:

// Разрушим значки установки и снятия маркера "галочки".

DeleteObject(hbmpCheck);
DeleteObject(hbmpUncheck);

PostQuitMessage(0);
break;

default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);

}
return NULL;
}

HBITMAP GetMyCheckBitmaps(fuCheck)
UINT fuCheck; // флажки CHECK или UNCHECK
{
COLORREF crBackground; // цвет фона
HBRUSH hbrBackground; // кисть фона
HBRUSH hbrTargetOld; // первоначальная кисть фона
HDC hdcSource; // исходный контекст устройства
HDC hdcTarget; // контекст устройства цели

HBITMAP hbmpCheckboxes; // дескриптор значка маркера
BITMAP bmCheckbox; // структура для данных значка
HBITMAP hbmpSourceOld; // дескриптор значка первоисточника
HBITMAP hbmpTargetOld; // дескриптор первоначального целевого значка
HBITMAP hbmpCheck; // дескриптор значка маркера
RECT rc; // прямоугольник для точечного рисунка окна маркера
DWORD dwCheckXY; // размеры значка маркера
WORD wBitmapX; // ширина значка маркера
WORD wBitmapY; // высота значка маркера

// Получим цвет фона меню и создадим объемную кисть с этим цветом.

crBackground = GetSysColor(COLOR_MENU);
hbrBackground = CreateSolidBrush(crBackground);

// Создадим контекст устройства в памяти для значков источника и цели.

hdcSource = CreateCompatibleDC((HDC) NULL);
hdcTarget = CreateCompatibleDC(hdcSource);

// Получим размер значка "галочки ", который Windows
// устанавливает по умолчанию и создадим совместимый точечный
// рисунок (значок) того же самого размера.


dwCheckXY = GetMenuCheckMarkDimensions();
wBitmapX = LOWORD(dwCheckXY);
wBitmapY = LOWORD(dwCheckXY);

hbmpCheck = CreateCompatibleBitmap(hdcSource, wBitmapX,
wBitmapY);

// Выберем кисть фона и значок в целевом значке.

hbrTargetOld = SelectObject(hdcTarget, hbrBackground);
hbmpTargetOld = SelectObject(hdcTarget, hbmpCheck);

// Используем выбранную кисть, чтобы инициализировать цвет фона
// Точечного рисунка (значка) в целевом контексте устройства.


PatBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, PATCOPY);

// Загрузим предопределенные точечные рисунки окошка
// для флажка и выберем его в исходном DC.


hbmpCheckboxes = LoadBitmap((HINSTANCE) NULL,
(LPTSTR) OBM_CHECKBOXES);

hbmpSourceOld = SelectObject(hdcSource, hbmpCheckboxes);

// Заполним структуру BITMAP информацией о точечном рисунке
// окошка для флажка, а затем найдем левый верхний угол окошка
для установки или снятия маркера (флажка) "галочки".


GetObject(hbmpCheckboxes, sizeof(BITMAP), &bmCheckbox);

if (fuCheck == UNCHECK) {
rc.left = 0;
rc.right = (bmCheckbox.bmWidth / 4);
}
else {
rc.left = (bmCheckbox.bmWidth / 4);
rc.right = (bmCheckbox.bmWidth / 4) * 2;
}

rc.top = 0;
rc.bottom = (bmCheckbox.bmHeight / 3);

//Копируем соответствующий точечный рисунок (значок) в целевом DC.
// Если точечный рисунок (значок) переключателя больше, чем заданный
// по умолчанию значок "галочки", используем StretchBlt,чтобы подогнать
// его по размерам; теперь копируем его.


if (((rc.right - rc.left) > (int) wBitmapX) ||
((rc.bottom - rc.top) > (int) wBitmapY))
StretchBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY,
hdcSource, rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, SRCCOPY);

else
BitBlt(hdcTarget, 0, 0, rc.right - rc.left,

rc.bottom - rc.top,
hdcSource, rc.left, rc.top, SRCCOPY);

// Выберем старый исходный и целевой точечные рисунки
// в исходном и целевом DC, а затем удалим DC и кисть фона.


SelectObject(hdcSource, hbmpSourceOld);
SelectObject(hdcTarget, hbrTargetOld);
hbmpCheck = SelectObject(hdcTarget, hbmpTargetOld);

DeleteObject(hbrBackground);
DeleteObject(hdcSource);
DeleteObject(hdcTarget);


// Возвратим дескриптор нового точечного рисунка "галочки ".

return hbmpCheck;
}


BYTE CheckOrUncheckMenuItem(bMenuItemID, hmenu)
BYTE bMenuItemID;
HMENU hmenu;
{
DWORD fdwMenu;
static BYTE fbAttributes;

switch (bMenuItemID) {
case IDM_REGULAR:

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



CheckMenuItem(hmenu, IDM_REGULAR, MF_BYCOMMAND |
MF_CHECKED);

if (fbAttributes & ATTRIBMASK) {
CheckMenuItem(hmenu, IDM_BOLD, MF_BYCOMMAND |
MF_UNCHECKED);
CheckMenuItem(hmenu, IDM_ITALIC, MF_BYCOMMAND |
MF_UNCHECKED);
CheckMenuItem(hmenu, IDM_ULINE, MF_BYCOMMAND |
MF_UNCHECKED);
}
fbAttributes = IDM_REGULAR;

return fbAttributes;

case IDM_BOLD:
case IDM_ITALIC:
case IDM_ULINE:


// Переключим галочку для выбранного пункта меню и
// установим флажки атрибута шрифта соответственно.


fdwMenu = GetMenuState(hmenu, (UINT) bMenuItemID,
MF_BYCOMMAND);
if (!(fdwMenu & MF_CHECKED)) {
CheckMenuItem(hmenu, (UINT) bMenuItemID,
MF_BYCOMMAND | MF_CHECKED);

fbAttributes |= bMenuItemID;

} else {
CheckMenuItem(hmenu, (UINT) bMenuItemID,
MF_BYCOMMAND | MF_UNCHECKED);
fbAttributes ^= bMenuItemID;
}

// Если какие-либо атрибуты шрифта в настоящее время
// выбраны, удалим галочку из пункта меню Regular; если
// никакие атрибуты не выбраны, добавим галочку к пункту
// меню Regular.


if (fbAttributes & ATTRIBMASK) {
CheckMenuItem(hmenu, IDM_REGULAR,
MF_BYCOMMAND | MF_UNCHECKED);
fbAttributes &= (BYTE) ~IDM_REGULAR;

} else {
CheckMenuItem(hmenu, IDM_REGULAR,
MF_BYCOMMAND | MF_CHECKED);
fbAttributes = IDM_REGULAR;
}

return fbAttributes;
}
}


Пример использования пользовательских точечных рисунков значков "галочки ".

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

Пункты меню во втором меню определяют параметры настройки выравнивания параграфа: слева, по центру и справа. Только один из этих пунктов меню в одно и тоже данное время может быть отмечен "галочкой". Для этих пунктов меню, пример использует значки, которые похожи на установленную и снятую метки типа радио-кнопки органа управления.

Оконная процедура обрабатывает сообщение WM_CREATE путем вызова определяемой программой функции OnCreate. OnCreate создает четыре значка "галочки", а затем связывает их со своими соответствующими пунктами меню при помощи использования функции SetMenuItemBitmaps.

Чтобы создать каждый значок, OnCreate вызывает определяемую программой функцию CreateMenuBitmaps, устанавливая указатель на конкретную функцию рисования для значка. Функция CreateMenuBitmaps создает одноцветный значок требуемого размера, выбирает его в памяти контекста устройства и стирает фон. Затем она вызывает заданную функцию рисования, чтобы заполнить передний план картинки.

Имеется четыре определяемые программой функции рисования - DrawCheck, DrawUncheck, DrawRadioCheck и DrawRadioUncheck. Они рисуют прямоугольник с X (крестиком), пустой прямоугольник, эллипс, содержащий меньший затемненный эллипс и, соответственно, пустой эллипс.

Оконная процедура обрабатывает сообщение WM_DESTROY, удаляя значки типа "галочки". Она получает дескриптор каждый картинки при помощи использования функции GetMenuItemInfo, а затем переправляет дескриптор функции DeleteObject.

Когда пользователь выбирает пункт меню, окну владельцу отправляется сообщение WM_COMMAND. Для пунктов меню в меню Character, оконная процедура вызывает определяемую программой функцию CheckCharacterItem. Для пунктов в меню Paragraph, оконная процедура вызывает определяемую программой функцию CheckParagraphItem.

Каждый пункт в меню Character, независимо друг от друга, может быть отмечен или не отмечен значком типа "галочки". Следовательно, CheckCharacterItem просто включает состояние проверки ("галочки") определяемого пункта меню. Сначала функция вызывает функцию GetMenuItemInfo, чтобы получить текущее состояние пункта меню. Затем она включает флажок состояния MFS_CHECKED и устанавливает новое состояние путем вызова функции SetMenuItemInfo.

В отличие от атрибутов символа, одновременно может быть выбрано только одно выравнивание абзаца. Следовательно, CheckParagraphItem отмечает "галочкой" определяемый пункт меню и удаляет галочку из всех других пунктов в меню. Чтобы сделать это, она вызывает функцию CheckMenuRadioItem.

Ниже следуют необходимые части заголовочного файла прикладной программы.

// Идентификаторы пунктов меню для меню Character

#define IDM_CHARACTER 10
#define IDM_BOLD 11
#define IDM_ITALIC 12
#define IDM_UNDERLINE 13

// Идентификаторы пункта меню для меню Paragraph

#define IDM_PARAGRAPH 20
#define IDM_LEFT 21
#define IDM_CENTER 22
#define IDM_RIGHT 23

// Тип указателя функции для функций рисования

typedef VOID (WINAPI * DRAWFUNC)(HDC hdc, SIZE size);

Ниже следуют необходимые части оконной процедуры прикладной программы и зависимых функций.

LRESULT CALLBACK MainWindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg) {
case WM_CREATE:
if (!OnCreate(hwnd))
return -1;
break;

case WM_DESTROY:
OnDestroy(hwnd);
PostQuitMessage(0);
break;

case WM_COMMAND:
switch (LOWORD(wParam)) {

case IDM_BOLD:

case IDM_ITALIC:

case IDM_UNDERLINE:
CheckCharacterItem(hwnd, LOWORD(wParam));
break;

case IDM_LEFT:
case IDM_CENTER:
case IDM_RIGHT:
CheckParagraphItem(hwnd, LOWORD(wParam));
break;

.
. // Обработка других команд здесь.
.


}

break;

.
. // Обработка других сообщений здесь.
.


default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}

VOID WINAPI CheckCharacterItem(HWND hwnd, UINT uID)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;

// Получим дескриптор меню Character.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем
GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);

hmenuPopup = mii.hSubMenu;

// Получим состояние заданного пункта меню.

mii.fMask = MIIM_STATE; // информация, которую получаем
GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);

// Переключим состояние отметки типа «галочки».

mii.fState ^= MFS_CHECKED;
SetMenuItemInfo(hmenuPopup, uID, FALSE, &mii);
}

VOID WINAPI CheckParagraphItem(HWND hwnd, UINT uID)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;

// Получим дескриптор меню Paragraph.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем
GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Отметим "галочкой" определяемый пункт и снимем отметку "галочкой" со всех других.

CheckMenuRadioItem(
hmenuPopup, // дескриптор меню
IDM_LEFT, // первый пункт в ряду
IDM_RIGHT, // последний пункт в ряду
uID, // пункт для проверки
MF_BYCOMMAND // ID, нет позиции
);
}

BOOL WINAPI OnCreate(HWND hwnd)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;
UINT uID;
HBITMAP hbmChecked;
HBITMAP hbmUnchecked;

// Получим дескриптор меню Character.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем
GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Создадим значки отметки "галочкой" и снятие отметки.

hbmChecked = CreateMenuBitmap(DrawCheck);
hbmUnchecked = CreateMenuBitmap(DrawUncheck);

// Установим точечные рисунки (значки) пометок для каждого пункта меню.

for (uID = IDM_BOLD; uID <= IDM_UNDERLINE; uID++) {
SetMenuItemBitmaps(hmenuPopup, uID, MF_BYCOMMAND,
hbmUnchecked, hbmChecked);
}

// Получим дескриптор выскакивающего меню Paragraph.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем

GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Создадим значки отметки "галочкой" и снятие отметки.

hbmChecked = CreateMenuBitmap(DrawRadioCheck);
hbmUnchecked = CreateMenuBitmap(DrawRadioUncheck);

// Установим точечные рисунки (значки) пометки для каждого пункта меню.

for (uID = IDM_LEFT; uID <= IDM_RIGHT; uID++) {
SetMenuItemBitmaps(hmenuPopup, uID, MF_BYCOMMAND,
hbmUnchecked, hbmChecked);

}

// Вначале отметим "галочкой) IDM_LEFT пункт параграфа.

CheckMenuRadioItem(hmenuPopup, IDM_LEFT, IDM_RIGHT,
IDM_LEFT, MF_BYCOMMAND);
return TRUE;
}

HBITMAP WINAPI CreateMenuBitmap(DRAWFUNC lpfnDraw)
{
// Создадим DC совместимый с DC рабочего стола окна.

HWND hwndDesktop = GetDesktopWindow();
HDC hdcDesktop = GetDC(hwndDesktop);
HDC hdcMem = CreateCompatibleDC(hdcDesktop);

// Установим требуемый растровый размер.


DWORD dwExt = GetMenuCheckMarkDimensions();
SIZE size = { LOWORD(dwExt), HIWORD(dwExt) };

// Создадим одноцветный значок и выберем его.

HBITMAP hbm = CreateBitmap(size.cx, size.cy, 1, 1, NULL);
HBITMAP hbmOld = SelectObject(hdcMem, hbm);

// Очистим фон и вызовем функцию рисования.

PatBlt(hdcMem, 0, 0, size.cx, size.cy, WHITENESS);
(*lpfnDraw)(hdcMem, size);

// Навести порядок.

SelectObject(hdcMem, hbmOld);

DeleteDC(hdcMem);
ReleaseDC(hwndDesktop, hdcDesktop);
return hbm;
}

VOID WINAPI DrawCheck(HDC hdc, SIZE size)
{
HBRUSH hbrOld;
hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, 0, 0, size.cx, size.cy);
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, size.cx, size.cy);
MoveToEx(hdc, 0, size.cy - 1, NULL);
LineTo(hdc, size.cx - 1, 0);
SelectObject(hdc, hbrOld);
}

VOID WINAPI DrawUncheck(HDC hdc, SIZE size)

{
HBRUSH hbrOld;
hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, 0, 0, size.cx, size.cy);
SelectObject(hdc, hbrOld);
}

VOID WINAPI DrawRadioCheck(HDC hdc, SIZE size)
{
HBRUSH hbrOld;
hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
Ellipse(hdc, 0, 0, size.cx, size.cy);
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Ellipse(hdc, 2, 2, size.cx - 2, size.cy - 2);
SelectObject(hdc, hbrOld);

}

VOID WINAPI DrawRadioUncheck(HDC hdc, SIZE size)
{
HBRUSH hbrOld;
hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
Ellipse(hdc, 0, 0, size.cx, size.cy);
SelectObject(hdc, hbrOld);
}

VOID WINAPI OnDestroy(HWND hwnd)
{
HMENU hmenuBar = GetMenu(hwnd);
HMENU hmenuPopup;
MENUITEMINFO mii;

// Получим дескриптор меню Character.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем
GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii);

hmenuPopup = mii.hSubMenu;

// Получим точечные рисунки (значки) "галочки " и удалим их.

mii.fMask = MIIM_CHECKMARKS;
GetMenuItemInfo(hmenuPopup, IDM_BOLD, FALSE, &mii);
DeleteObject(mii.hbmpChecked);
DeleteObject(mii.hbmpUnchecked);

// Получим дескриптор меню Paragraph.

mii.fMask = MIIM_SUBMENU; // информация, которую получаем
GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii);
hmenuPopup = mii.hSubMenu;

// Получим точечные рисунки (значки) "галочки " и удалим их.


mii.fMask = MIIM_CHECKMARKS;
GetMenuItemInfo(hmenuPopup, IDM_LEFT, FALSE, &mii);
DeleteObject(mii.hbmpChecked);
DeleteObject(mii.hbmpUnchecked);
}