Главная » C++, C++ Builder » Сами рисуем свое меню C++ Builder

0

Чуть ранее мы обсудили списки, прорисовываемые их владельцем. Такие списки позволяли вам отображать в качестве элемента списка все, что захотите (текст, графику, цвета, шрифты). Наверное, вы уже обратили внимание на то, что меню похоже на список. Оба эти объекта позволяют отображать некий список элементов. В этом они очень схожи. Еще они схожи в следующем: и тот, и другой позволяют отображать элементы списка в каком угодно формате. С этой точки зрения, меню даже лучше списков. Меню, как мы увидим в этом примере, позволяет вам отображать только те элементы, которые вы действительно хотите отобразить, и даже позволяет во время исполнения выбирать — осуществляет ли меню отображение того или иного элемента в каком-то своем формате, или нет. Итак, в этом примере мы рассмотрим некоторые возможности отображения элементов меню в CBuilder, а также возможности изменения их поведения в соответствии с целями и задачами вашего приложения.

Замечание

Вы  найдете  исходный  код  примера  программы  работы  с  собственными  меню  в  директории

Chapter4\OwnerDrawMenu  прилагаемого  компакт-диска.

Если вы помните, в разговоре об отображаемых владельцем списках мы упоминали о том, что для обработки такого отображения вам необходимо отследить два сообщения Windows. Первое сообщение — это WM_MEASUREITEM, которое посылается каждому элементу в меню. Это сообщение подразумевает, что вы сообщили меню, насколько высоким каждый элемент должен быть на экране. Сообщение Windows WM_MEASUREITEM чудным образом заключено в событие OnMeasureItem форм CBuilder.

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

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

Имеем дело с Windows API

Если вы просмотрите методы компонентов VCL TMainMenu и TMenuItem, то не найдете среди них ни одного, который бы был напрямую ассоциирован с меню, отображаемыми их владельцем. И все по одной простой причине — таких там нет. Так что, если вы всерьез захотите заиметь такие в своем приложении, вам придется иметь дело непосредственно с Windows API. Не бойтесь — это не будет кошмаром, как было бы, работай вы в Visual Basic или даже Delphi, которые применяют отличные от API описания переменных. Вместо этого вы работаете на C++, языке,  который отлично совместим с языком C, на котором и был, собственно, написан API. Большой разговор о функциях Windows API и CBuilder предстоит нам в следующей главе.

Функция  Windows  API,   с  которой  нам  придется  столкнуться   в   этом   примере,  называется

ModifyMenu. Функция ModifyMenu имеет следующий синтаксис:

ModifyMenuA( HMENU hMnu, UINT uPosition, UINT uFlags,

UINT uIDNewItem,

LPCSTR lpNewItem

);

где hMnu — ссылка (handle) на меню; uPosition — позиция элемента меню, который вы собираетесь изменить внутри меню; uFlags — множество возможных флагов, используемых при изменении этого элемента меню; uIDNewItem — идентификатор изменяемого  элемента; lpNewItem — либо строка, отображаемая в качестве элемента меню, либо ссылка на меню, в зависимости от параметра uFlags.

Параметр uFlags — это то самое место, с которым связана всю путаница в работе с этой функцией API. Как правило вы будете определять его как MF_BYCOMMAND или MF_BYPOSITION. Значение MF_BYCOMMAND означает, что параметр uIDNewItem определяет идентификатор элемента меню (значение, которое мы определяем в свойстве Command элемента меню). Значение же MF_BYPOSITION показывает, что параметр uIDNewItem определяет отсчитывающийся от 0 индекс в список элементов меню для ссылки на меню. Использование этого аргумента будет означать, что первое значение будет 0 вне зависимости от значения свойства Command.

Вместе с флагом MF_BYCOMMAND или MF_BYPOSITION, вы  можете  установить  еще  один флаг, определяющий, какого типа будет элемент меню. Это может быть MF_STRING, MF_BITMAP или MF_OWNERDRAW. В случае MF_STRING параметр lpNewItem  будет указывать на  строку, используемую как текст элемента меню. В случае MF_BITMAP параметр будет являться ссылкой на растровый рисунок, а флаг MF_OWNERDRAW означает, что параметр не важен. Аргумент MF_BITMAP мы не будем использовать в данном примере.

Чем тратить прорву времени на объяснение  того, каким образом  различные флаги могут быть использованы и скомбинированы, куда проще показать это на примере. Итак, добавьте элемент главного меню с именем «Fred» в  меню. Потом добавьте второй элемент с именем «Irving» и третий, с названием «Изменить». В пункт Fred  добавьте два подпункта   с именами  «George» и

«Ralph».  Это  и  будут  наши  элементы  меню,  отображаемые их владельцем.  Исключительно для

того, чтобы показать, что изменения в одном месте не влияют на элементы меню  в  другом, добавьте подпункт в пункт меню Irving с любым, каким вам будет угодно, именем. В пункт меню Изменить добавьте два подпункта с именами «Сделать отображаемыми владельцем» и «Сделать нормальными». Эти два пункта будут использоваться для переключения состояний одного  из наших элементов.

Добавьте  новый  обработчик  для  события  формы  OnCreate.  Присвойте  этому обработчику  имя

FormCreate. В этот новый обработчик добавьте следующий код:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

ModifyMenu(MainMenu1->Handle, George1->Command,

MF_BYCOMMAND | MF_OWNERDRAW,

George1->Command, 0);

ModifyMenu(MainMenu1->Handle, Ralph1->Command,

MF_BYCOMMAND | MF_OWNERDRAW,

Ralph1->Command, 0);

}

Как вы видите, мы используем функцию API ModifyMenu для того, чтобы изменить два элемента меню (George и Ralph соответственно) так, чтобы они стали элементами, отображаемыми их владельцем. Команда ModifyMenu будет пытаться изменить команду, ассоциированную  с элементом меню, так что нам придется передавать ее дважды, чтобы избежать изменений. Параметр нового элемента меню (последний) в данном случае не используется, так что мы просто передаем команде 0.

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

void __fastcall TForm1::George1Click(TObject *Sender)

{

MessageBox(NULL, "Вы выбрали Джорджа!", "Подтверждение", MB_OK );

}

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

Отображение элементов меню

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

Добавьте обработчик для события формы OnMeasureItem и назовите его HandleMeasureItem, а в него добавьте следующий код:

void __fastcall TForm1::HandleMeasureItem(TMessage& Msg)

{

MEASUREITEMSTRUCT *lpmis = (LPMEASUREITEMSTRUCT)  Msg.Lparam;

lpmis->itemHeight = 12;

lpmis->itemWidth = 50;

}

Этот метод всего лишь модифицирует структуру Measure Item. Обратите внимание на то, что поскольку направленное нам сообщение является объектом TMessage, мы должны преобразовать часть его (LParam) в указатель на структуру, с которой будем работать. Работа с меню, отображаемыми их владельцем не настолько прозрачна, как с элементами списка, но все же достаточно проста.

Далее, добавьте обработчик для метода формы OnDrawItem. Присвойте новому обработчику имя

HandleDrawItem и добавьте в него следующий код:

void __fastcall TForm1::HandleDrawItem(TMessage& Msg)

{

DRAWITEMSTRUCT *lpdis = (DRAWITEMSTRUCT *) Msg.LParam;

TCanvas *canvas = new TCanvas; canvas->Handle = lpdis->hDC;

if ((int)lpdis->itemID == George1->Command )

canvas->Brush->Color = clRed; else

canvas->Brush->Color = clGreen;

// Определяем прямоугольник

TRect r;

r.Left = lpdis->rcItem.left; r.Top = lpdis->rcItem.top; r.Right = lpdis->rcItem.right;

r.Bottom = lpdis->rcItem.bottom;

canvas->FillRect(r); delete canvas;

}

Еще раз, поскольку  воплощение меню, отображаемого владельцем, не столь просто, как аналогичного списка, отмечу, что вы должны преобразовать часть объекта-структуры TMessage в указатель на объект DRAWITEMSTRUCT, который содержит все куски кода для отрисовки меню. Имея структуру, мы используем ссылку  (handle) на контекст устройства (device  context), хранящуюся в этом объекте, для создания нового объекта Canvas для использования в коде отрисовки. Работать с объектом Canvas куда проще, чем со ссылкой на контекст устройства, лежащего в его основе, так что это делается для нашей же пользы. Все, что нам остается сделать, это определить, какой цвет выбрать, отталкиваясь от идентификатора itemID в структуре и заполнить этим цветом прямоугольник, который передается нам в структуре. Просто, как пареная репа.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии