Adding vertical text and a color bar to a popup menu - updated

By: Clayton Todd

Abstract: Owner-draw components let you customize the appearance and behavior of Windows with C++Builder.

Adding vertical text and a color bar to a popup menu

Owner-draw components let you customize the appearance and behavior of Windows with C++Builder.

Download the updated article from CodeCentral. The update is more customizable and has better menu item drawing code. It does not include vertical text like this article.

Example of update

By Clayton Todd

Some of the components in C++Builder have a property called OwnerDraw. This allows you to set it up so the application automatically handles the drawing of the item or you can write your own code to handle all the drawing.

The TPopupMenu component has this property, and by setting it to true we can draw some shapes, and yes, cool things on the popup menu. This will also work for the TMenu component but for this article I will talk only about the TPopupMenu. This example demonstrates creating a popupmenu with vertical text and a color bar similar to the Windows Start button menu. Here's what the menu looks like when you're done:

Let's get coding. Here's what you do:

Download the project from Code Central.

Create a new project.

Add a PopupMenu component.

Add a ImageList component.

Set the form's PopupMenu property to the PopupMenu you added.

Set the PopupMenu's OwerDraw property to true.

Set the PopupMenu's Images property to the ImageList you added.

Add some images to the ImageList.

Add some items to the PopupMenu.

Add the following code to your header files.

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;

Add the following code to the private section of the class.

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;
The rest of the code goes in your source file.

We want to replace the default OnMeasureItem and OnDrawItem with our own methods. These events occur on owner drawn menu items.

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)
{
   // We need to make the Menu wider to make room for the vertical font
   Width += TEXT_SPACE;
}

//---------------------------------------------------------------------------

Check out the LOGFONT structure in the Windows SDK for more options.

void TForm1::CreateVerticalFont()
{
   ZeroMemory(&VerticalFont,sizeof(VerticalFont));
   VerticalFont.lfHeight = -18;
   VerticalFont.lfEscapement = 900;
   VerticalFont.lfOrientation = 900;
   VerticalFont.lfWeight = FW_BOLD;
   StrPCopy(VerticalFont.lfFaceName, "Arial");
}
//---------------------------------------------------------------------------
Since the PopupMenu's OwnerDraw property is true we need to do all the drawing.

First we need to draw the vertical text and color bar -- otherwise this article would not be titled "Adding vertical text and a color bar to a popup menu." Then we need to draw the text for the menu item, draw the icons, and draw the selection rectangle for the menu item and the icon.

You have access to the Canvas of the PopupMenu. Let the drawing begin.

void __fastcall TForm1::DrawNewItem(TObject *Sender, TCanvas *ACanvas,
   const TRect &ARect, bool Selected)
{
   TMenuItem *MenuItem = ((TMenuItem*)Sender);

   // We get the default size that is set aside on the menu for the checkmark
   CheckmarkSize = GetSystemMetrics(SM_CXMENUCHECK);
   MenuHeight = ARect.Height() * MenuItem->Parent->Count;
   VerticalBarLength = MenuHeight / 4;

   // Only going to draw the vertical text once.
   // However if the desktop refreshes, bye bye vertical text and color bar
   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);
   // Instead of drawing a filled rect, I cheat.
   // I make the string slightly longer then the popupmenu
      VerticalText = VerticalText.StringOfChar(' ',VerticalBarLength);
      VerticalText.Insert(" Vertical Text",1);

   // Draw the longer string which produces the color bar
      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;
   // Make room for our icon
   TempRect.Left += LOWORD(CheckmarkSize)+ICON_SPACE;

   ACanvas->Pen->Style = psClear;

   // Fix the selection rectangle.  Run the program with out this and see the difference.
   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 the menu item is selected draw a raised rect around the icon.
   // Else erase the raised rectangle
   if(Selected)
   {
   // Offset 2 rects for 3d look.
      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);

      // Here is where we retrieve the icon from the 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)
{
   // Clean up after our messy selves
   delete Icon;
}
//---------------------------------------------------------------------------

If you have questions or comments, feel free to contact me.


Server Response from: ETNASC04