Добавление вертикального текста и цветной полосы во всплывающее меню
Оригинал находится здесь.
Автор Clayton Todd.
орока.
Загрузите обновленную версию статьи с CodeCentral. Обновление предоставляет более широкие возможности и имеет лучший код рисования пункта меню. Оно не включает вертикальный текст, в отличие от этой статьи.

Рис. 1. Пример из обновления
У некоторых компонентов C++Builder 'а есть свойство OwnerDraw. Это позволяет вам устанавливать его значение таким образом, что либо приложение автоматически выполняет отрисовку элемента, либо вы выполняете отрисовку самостоятельно, написав соответствующий код.
У компонента TPopupMenu присутствует это свойство, и, устанавливая его значение в true, мы можем во всплывающем меню рисовать всякие интересные вещи. Это также справедливо для компонента TMenu, но в этой статье я буду говорить только о TPopupMenu. Эта статья демонстрирует создание всплывающего меню с вертикальным текстом и цветной полосой, похожего на главное меню Windows, появляющееся при нажатии кнопки Пуск. Ниже показано, как будет выглядеть ваше меню:

Рис. 2. Внешний вид меню, рассматриваемого в данной статье.
Итак, начинаем. Вот что вам надо сделать:
1. Загрузите проект с Code Central.
2. Создайте новый проект.
3. Добавьте компонент всплывающего меню (PopupMenu).
4. Добавьте компонент списка изображений (ImageList).
5. Установите значение свойства PopupMenu формы в добавленное вами всплывающее меню.
6. Установите значение свойства OwnerDraw всплывающего меню в true.
7. Установите значение свойства Images всплывающего меню в добавленный вами ImageList.
8. Добавьте изображения в ImageList.
9. Добавьте элементы в PopupMenu.
10. Добавьте следующий код в ваши заголовочные файлы:
const TEXT_SPACE = 15;
const ICON_SPACE = 35;
const MENU_TEXT_HEIGHT = 18;
const SPACE_BETWEEN_MENUS = 1;
const MENU_TEXT_LEFT = 2;
const MENU_ITEM_OFFSET = 19;
Добавьте следующий код в закрытую часть класса:
private:
void __fastcall ExpandMenuItemWidth(TObject *Sender, TCanvas *ACanvas,
int &Width, int &Height);
void __fastcall DrawNewItem(TObject *Sender, TCanvas *ACanvas,
const TRect &ARect, bool Selected);
void CreateVerticalFont();
TLogFont VerticalFont;
TIcon *Icon;
TFont *OldFont;
TRect VerticalDrawingRect,
TempRect;
AnsiString VerticalText;
int MenuHeight,
VerticalBarLength;
long int CheckmarkSize,
OldForegroundColor,
OldBackgroundColor;
bool VerticalBarDrawn;
Мы хотим заменить стандартные OnMeasureItem и OnDrawItem нашими собственными обработчиками событий. Эти события происходят при самостоятельной отрисовке пунктов меню.
void __fastcall TForm1::FormCreate(TObject *Sender)
{
if(PopupMenu1->Items->Count > 0)
{
for(int i=0; i <= PopupMenu1->Items->Count-1; i++)
{
PopupMenu1->Items->Items[i]->OnMeasureItem = ExpandMenuItemWidth;
PopupMenu1->Items->Items[i]->OnDrawItem = DrawNewItem;
}
}
CreateVerticalFont();
Icon = new TIcon;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PopupMenu1Popup(TObject *Sender)
{
VerticalBarDrawn = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ExpandMenuItemWidth(TObject *Sender, TCanvas *ACanvas, int &Width, int &Height)
{
// необходимо сделать меню шире, чтобы оставить место для вертикального
// текста
Width += TEXT_SPACE;
}
//---------------------------------------------------------------------------
Для дополнительной информации о структуре LOGFONT смотрите Windows SDK.
void TForm1::CreateVerticalFont()
{
ZeroMemory(&VerticalFont, sizeof(VerticalFont));
VerticalFont. lfHeight = -18;
VerticalFont. lfEscapement = 900;
VerticalFont. lfOrientation = 900;
VerticalFont. lfWeight = FW_BOLD;
StrPCopy(VerticalFont. lfFaceName, "Arial");
}
//---------------------------------------------------------------------------
Поскольку значение свойства OwnerDraw PopupMenu установлено в true, нам необходимо самим выполнять всю отрисовку.
Сначала нам необходимо нарисовать вертикальный текст и цветную полосу - иначе бы статья не называлась "Добавление вертикального текста и цветной полосы во всплывающее меню". Затем нам необходимо нарисовать текст для пунктов меню, нарисовать иконки и прямоугольник выбора для пункта меню и иконки.
У вас есть доступ к свойству Canvas PopupMenu. Давайте начнем рисование:
void __fastcall TForm1::DrawNewItem(TObject *Sender, TCanvas *ACanvas,
const TRect &ARect, bool Selected)
{
TMenuItem *MenuItem = ((TMenuItem*)Sender);
// получаем размеры, необходимые для отметки пункта меню
CheckmarkSize = GetSystemMetrics(SM_CXMENUCHECK);
MenuHeight = ARect. Height() * MenuItem->Parent->Count;
VerticalBarLength = MenuHeight / 4;
// собираемся рисовать вертикальный текст только один раз
// тем не менее, если рабочий стол обновляется, то вертикальный текст
// исчезает
if(!VerticalBarDrawn)
{
OldFont = (TFont*)SelectObject(ACanvas->Handle, CreateFontIndirect(&VerticalFont));
OldForegroundColor = SetTextColor(ACanvas->Handle, clWhite);
OldBackgroundColor = SetBkColor(ACanvas->Handle, clRed);
VerticalDrawingRect = Rect(0, 0, CheckmarkSize+TEXT_SPACE, MenuHeight);
// я делаю строку немного длиннее, чем всплывающее меню
VerticalText = VerticalText. StringOfChar(' ',VerticalBarLength);
VerticalText. Insert(" Vertical Text",1);
ExtTextOut(ACanvas->Handle, -1, MenuHeight, ETO_CLIPPED,
&VerticalDrawingRect, VerticalText. c_str(), VerticalBarLength, NULL);
SelectObject(ACanvas->Handle, OldFont);
SetTextColor(ACanvas->Handle, OldForegroundColor);
SetBkColor(ACanvas->Handle, OldBackgroundColor);
VerticalBarDrawn = true;
}
TempRect = ARect;
// оставляем место для иконки
TempRect. Left += LOWORD(CheckmarkSize)+ICON_SPACE;
ACanvas->Pen->Style = psClear;
// прямоугольник выбора
// запустите программу без этого и посмотрите разницу
ACanvas->Rectangle(
TempRect. Left-MENU_TEXT_LEFT,
MenuItem->MenuIndex*MENU_ITEM_OFFSET-SPACE_BETWEEN_MENUS,
ARect. Width(),
MenuItem->MenuIndex*MENU_ITEM_OFFSET+MENU_TEXT_HEIGHT);
DrawText(ACanvas->Handle, MenuItem->Caption. c_str(),MenuItem->Caption. Length(),
&TempRect, 0);
// если пункт меню выбран, рисуем приподнятый прямоугольник вокруг иконки
// иначе стираем приподнятый прямоугольник
if(Selected)
{
// смещаем 2 прямоугольника для объемного вида
ACanvas->Pen->Style = psSolid;
ACanvas->Pen->Color = clWhite;
ACanvas->Rectangle(
24,
MenuItem->MenuIndex * MENU_ITEM_OFFSET-1,
24+MENU_ITEM_OFFSET,
MenuItem->MenuIndex * MENU_ITEM_OFFSET+MENU_TEXT_HEIGHT-1);
ACanvas->Pen->Color = clGray;
ACanvas->Rectangle(
25,
MenuItem->MenuIndex * MENU_ITEM_OFFSET,
24+MENU_ITEM_OFFSET,
MenuItem->MenuIndex * MENU_ITEM_OFFSET+MENU_TEXT_HEIGHT-1);
// здесь мы извлекаем иконку из ImageList
ImageList1->GetIcon(MenuItem->ImageIndex, Icon);
ACanvas->Draw(26,MenuItem->MenuIndex * MENU_ITEM_OFFSET, Icon);
}
else
{
ACanvas->Pen->Style = psClear;
ACanvas->Rectangle(
24,
MenuItem->MenuIndex * MENU_ITEM_OFFSET-2,
25+MENU_ITEM_OFFSET+2,
MenuItem->MenuIndex * MENU_ITEM_OFFSET+MENU_TEXT_HEIGHT+2);
ImageList1->GetIcon(MenuItem->ImageIndex, Icon);
ACanvas->Draw(26,MenuItem->MenuIndex * MENU_ITEM_OFFSET, Icon);
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
delete Icon;
}
//---------------------------------------------------------------------------


