Добавление вертикального текста и цветной полосы во всплывающее меню

Оригинал находится здесь.
Автор 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;
}
//---------------------------------------------------------------------------